<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>j0e</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/" />
    <link rel="self" type="application/atom+xml" href="http://blogs.perl.org/users/j0e/atom.xml" />
    <id>tag:blogs.perl.org,2009-11-03:/users/j0e//1287</id>
    <updated>2013-05-15T00:20:20Z</updated>
    <subtitle>A blog about the Perl programming language</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type Pro 4.38</generator>

<entry>
    <title>Notes from a Newbie 16: Deploy</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4676</id>

    <published>2013-05-13T23:23:50Z</published>
    <updated>2013-05-15T00:20:20Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<p>Now that we've developed a simple, functional application I will give you some information and advice about deploying it to a shared host.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a100'>Choosing a Shared Host</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a200'>asmallorange.com</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a101'>Deploying yardbirdfanclub.org to a Shared Host</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a201'>Resources</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a202'>asmallorange.com</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a300'>Support and Account Center</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a301'>cPanel</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a302'>Getting Started</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a303'>Make Distribution, Copy to Server and Unpack Tarball</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a400'>Make Distribution</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a401'>Copy to Server</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a402'>Unpack Tarball</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a304'>Setup .htaccess and Symlink</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a403'>Create the Symlink</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a404'>Edit .htaccess</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a305'>Setup the Database</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a405'>Create a MySQL "Dump File" of the Database</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a406'>Copy Dump File to Database Server</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a407'>Create New Database and Users in cPanel</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a408'>Initialize Database on Database Server</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a409'>Configure the Application</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a306'>Install Local Perl</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a307'>Install local::lib?</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a308'>Install App::cpanminus</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a309'>Install Modules</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a310'>\o/</a></li>
</ul>
</ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a102'>Other Deployment Options</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a203'>VPS</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a204'>dotCloud</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a103'>What's Next?</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a205'>Apply for Jobs</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a206'>Notes from a Newbie: YAPC::NA 2013, Austin, Texas</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a207'>Improve yardbirdfanclub.org?</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html#a104'>Where to Find the Yardbird Source Code</a></li>
</ul></p>
]]>
        <![CDATA[<p><a name='a100'></a></p>

<h1>Choosing a Shared Host</h1>

<p>When I began to look for a place to deploy <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> I started here:</p>

<ul>
<li><a href='http://wiki.catalystframework.org/wiki/hosting/' target='_blank'>wiki.catalystframework.org/wiki/hosting/</a> </li>
</ul>

<p>I decided to go with <a href='http://www.pair.com/' target='_blank'>pair.com</a> and signed-up on their website for their "pairLite" plan. After initial attempts to deploy failed, I contacted customer support and was told a Perl Catalyst application would not run on the plan I chose. I was advised to go with their pairVPS (Virtual Private Servers) plan, but I couldn't afford it. Charges were refunded, the "pairLite" account closed and an account with their "Basic" plan opened:</p>

<pre><code>At this point the pairLite account is being closed, and the charges refunded. You should see the credit post back to your credit card in the next day, or so.

In addition, I also went ahead and setup the Basic account that utilizes our standard pair.com servers that include some more features that aren't available with pairLite.

As I mentioned, this is something that's typically not done on this lower tiered style of account, but considering your needs it seems like it would be worth a shot.

If you still aren't able to get things running on the new account just let me know, and I can get this one closed for you as well. If it does work out for you the normal charges of $10/month would take effect starting February 1st.
</code></pre>

<p>I continued to try and deploy my application with a little help from customer support and people at irc.perl.org, but ultimately failed and the account was closed.</p>

<p>Although attempts to deploy with <a href='http://www.pair.com/' target='_blank'>pair.com</a> failed, not all was lost and I have the utmost respect for them. Telephone support was prompt and courteous and several people sincerely tried to help, though they always prefaced our conversation with something like:</p>

<pre><code>We only support PHP with fastcgi for this type of account. You may be able to get your Perl Catalyst application to run, but we don't support it.
</code></pre>

<p>I only called a few times, but the last person I talked to stated definitively that a Catalyst application would not run on the account. By then I was very frustrated, so I gave up.</p>

<p>In fairness to <a href='http://www.pair.com/' target='_blank'>pair.com</a>, I had practically no system administration experience going into this, and no experience deploying any kind of website on any kind of a server. With the experience I have since gained, I might be able to successfully deploy if I tried to do so again.</p>

<p><a name='a200'></a></p>

<h2>asmallorange.com</h2>

<p>I went back to <a href='http://wiki.catalystframework.org/wiki/hosting/' target='_blank'>wiki.catalystframework.org/wiki/hosting/</a> and continued my research, this time inquiring at irc.perl.org and surfing the Internet as well. I emailed and called several providers that interested me, and <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a> was the only one that promptly responded. Among other things I asked:</p>

<pre><code>I am looking for shared web hosting to deploy my perl catalyst application, this is what I would like to do:

Install the current maintenance version 5.16.2 of perl in my home directory, along with perl modules needed by my catalyst app. I want to deploy it with my local perl rather than the system perl, using apache's mod_fastcgi. Will you support that?
</code></pre>

<p>Among other things, the reply was:</p>

<pre><code>All of your technical specs sound totally doable.
</code></pre>

<p>I liked what I saw at their website, found many happy customers on the Internet, and was satisfied with their responses to my inquires, so I signed-up for their medium shared hosting and am glad I did.</p>

<p>It was fortunate that I asked the above question, and I would advise you to do the same before signing up with anybody for shared hosting, including <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a>. The problem with shared hosting is that you don't have root access to the server, leaving you at the mercy of your provider to configure anything that requires root access. My deployment required two such administrative actions:</p>

<ul>
<li>Enable "gcc" and "as" compiler access. </li>
<li>Resolve localhost in the /etc/hosts file, i.e. fix the issue that prevents '127.0.0.1' resolving.</li>
</ul>

<p>I initiated support tickets and found that doing so was a waste of time. Each time I was passed onto somebody else, a half-dozen or more times, each time getting a more stern response stating that my request was not possible for shared hosting and that I needed to upgrade to a Cloud VPS account. After a day or two I gave up on normal support channels and emailed the systems administrator who answered my question above, and he happily solved the issues in a matter of seconds. If it wasn't for him I would have again failed to deploy with shared hosting.</p>

<p>From here on out it's been smooth sailing with <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a>, unfortunately winds change. The issue preventing '127.0.0.1' from resolving should no-longer be a problem, but compiler access will be. Compiler permissions are automatically reset by consistency checks, so I only have compiler access for 24-hours or so before having to again request access. Ouch. Do all module installs/updates require "gcc" and "as" compiler access? I dunno, I have yet to try.</p>

<p><a name='a101'></a> </p>

<h1>Deploying yardbirdfanclub.org to a Shared Host</h1>

<p><a name='a201'></a></p>

<h2>Resources</h2>

<p>People on the #catalyst channel at irc.perl.org were perhaps my most valuable resource, pointing me in the right direction and helping me solve problems. They were extremely patient and generous with their time and skills.</p>

<p>Though I lacked basic system administration and other skills necessary to implement them, I found the Perl documentation to be 100% complete with all the Catalyst and server specific details I needed to deploy my Catalyst application:</p>

<ul>
<li>Catalyst::Manual::Deployment</li>
<li>Catalyst::Manual::Deployment::SharedHosting</li>
<li>Catalyst::Manual::Deployment::FastCGI</li>
</ul>

<p>Since I deployed to an Apache server I referred to the following documentation, but documentation for other servers exist as well:</p>

<ul>
<li>Catalyst::Manual::Deployment::Apache::FastCGI</li>
</ul>

<p>I also needed the local::lib documentation to bootstrap local::lib:</p>

<ul>
<li>local::lib</li>
</ul>

<p>I used the Internet to discover and learn how to use shell commands I had not used before:</p>

<ul>
<li>ssh</li>
<li>wget</li>
<li>scp</li>
<li>tar</li>
<li>ln</li>
</ul>

<p>Though it takes time and I sometimes rely on them too much, I use the vimwiki plugin with vim to take notes - they are a necessary resource for almost everything I do with a computer.</p>

<p>Hopefully your shared hosting provider provides good notes and support that you will need to deploy on their servers.</p>

<p><a name='a202'></a></p>

<h2>asmallorange.com</h2>

<p>I signed-up for medium shared hosting at <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a>.</p>

<p>They provide two resources that I frequently used:</p>

<p><a name='a300'></a></p>

<h3>Support and Account Center</h3>

<ul>
<li>View and Update Accounts</li>
<li>Manage Hosting Packages</li>
<li>Manage Domain Names</li>
<li>Order New Products</li>
<li>Submit, View, and Respond to Support Tickets</li>
<li>Access Knowledgebase and Tutorials</li>
</ul>

<p><a name='a301'></a></p>

<h3>cPanel</h3>

<ul>
<li>Manage SSH Access</li>
<li>Manage FTP Accounts</li>
<li>Manage Domains</li>
<li>Manage Databases</li>
<li>Manage Email Accounts</li>
<li>Access Server Error Logs</li>
</ul>

<p>I think some cPanel features may be broken on shared host accounts, for example the feature to install Perl modules - I got errors when attempting to do so.</p>

<p><a name='a302'></a></p>

<h3>Getting Started</h3>

<p>After signing-up for medium shared hosting I received emails that got me started and were easy to follow. They included information about:</p>

<ul>
<li>Server Names and IP addresses</li>
<li>Nameservers and Domains</li>
<li>Email Settings</li>
<li>Passwords</li>
</ul>

<p>I needed to submit a support ticket to gain ssh access, and within 15 minutes or so the ticket was acted upon and I closed it.</p>

<p>I tried unsuccessfully to get ssh to connect to the server using a private key generated by cPanel, but I was able to do so with a public/private RSA key pair generated from my client.</p>

<p><a name='a303'></a></p>

<h3>Make Distribution, Copy to Server and Unpack Tarball</h3>

<p><a name='a400'></a></p>

<h4>Make Distribution</h4>

<pre><code>[j0e@axe Yardbird]$ perl Makefile.PL
[j0e@axe Yardbird]$ make manifest
[j0e@axe Yardbird]$ make dist
</code></pre>

<p><a name='a401'></a></p>

<h4>Copy to Server</h4>

<pre><code>[j0e@axe ~]$ scp -p Yardbird.tar.gz accountname@servername.asmallorange.com:Yardbird.tar.gz
</code></pre>

<p><a name='a402'></a></p>

<h4>Unpack Tarball</h4>

<p>I created the ~/lib directory on the server and moved the tarball there. I renamed the new unpacked directory Yardbird so that I would have ~/lib/Yardbird:</p>

<pre><code>accountname@yardbirdfanclub.org [~/lib]# tar -xzf Yardbird.tar.gz
</code></pre>

<p>I renamed <code>yardbird_fastcgi.pl</code> to <code>yardbird_fastcgi.fcgi</code>.</p>

<p><a name='a304'></a></p>

<h3>Setup .htaccess and Symlink</h3>

<p><a name='a403'></a></p>

<h4>Create the Symlink</h4>

<pre><code>accountname@yardbirdfanclub.org [~/public_html]# ln -s ~/lib/Yardbird/script script
</code></pre>

<p><a name='a404'></a></p>

<h4>Edit .htaccess</h4>

<p>~/public_html/.htaccess:</p>

<pre><code>RewriteEngine On
RewriteCond %{REQUEST_URI} !^/?script/yardbird_fastcgi.fcgi
RewriteRule ^(.*)$ script/yardbird_fastcgi.fcgi/$1 [PT,L]
</code></pre>

<p>See:</p>

<ul>
<li>Catalyst::Manual::Deployment::SharedHosting </li>
</ul>

<p><a name='a305'></a></p>

<h3>Setup the Database</h3>

<p>I used my <code>yardbird.sql</code> and <code>set_hashed_passwords.pl</code> script we created and used in <a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html' target='_blank'>Notes from a Newbie 10: Authentication/Authorization</a> to create a new database on my production machine, initialized with only a webmaster and no blog entries or comments.</p>

<p>Use yardbird.sql to create a new database:</p>

<pre><code>[j0e@axe Yardbird]$  mysql -uusername -ppassword
mysql&gt; create database yardbird;
mysql&gt; quit;
[j0e@axe Yardbird]$  mysql -uusername -ppassword yardbird &lt; yardbird.sql
</code></pre>

<p>Load hashed passwords into the database:</p>

<pre><code>[j0e@axe Yardbird]$ perl -Ilib set_hashed_passwords.pl
</code></pre>

<p>Then I created a MySQL dump file of this new database and copied the dump file to the database server. Next I created a new database and users in cPanel, and initialized the new database on the server with the dump file. Finally, I configured my Yardbird application on the server by updating the connect_info in ~/lib/Yardbird/lib/Yardbird/Model/DB.pm.</p>

<p><a name='a405'></a></p>

<h4>Create a MySQL "Dump File" of the Database</h4>

<pre><code>[j0e@axe Yardbird]$ mysqldump -Qq -uusername -ppassword yardbird &gt; yardbird_dump.sql
</code></pre>

<p><a name='a406'></a></p>

<h4>Copy Dump File to Database Server</h4>

<pre><code>[j0e@axe ~]$ scp -p yardbird_dump.sql accountname@databaseservername.asmallorange.com:yardbird_dump.sql
</code></pre>

<p><a name='a407'></a></p>

<h4>Create New Database and Users in cPanel</h4>

<p>In cPanel go to MySQL Databases.</p>

<p>Create New Database: <code>yardbird</code></p>

<p>Add New User: <code>admin_user</code></p>

<p>Add User To Database: Add <code>admin_user</code> to the new <code>yardbird</code> database with all privileges. This user will initialize the database with the dump file, manage tables and do other database maintenance not done by the catalyst application, but I need another user with basic access for my Yardbird application:</p>

<p>Add New User: <code>yardbird_user</code></p>

<p>Add User To Database: Add <code>yardbird_user</code> to the new <code>yardbird</code> database with limited privileges.</p>

<p><a name='a408'></a></p>

<h4>Initialize Database on Database Server</h4>

<p>Use <code>yardbird_dump.sql</code> to create and initialize tables:</p>

<pre><code>accountname@databaseservername [~]# mysql -uadmin_user -p yardbird &lt; yardbird_dump.sql
</code></pre>

<p><a name='a409'></a></p>

<h4>Configure the Application</h4>

<p>I configured my Yardbird application to connect to the <code>yardbird</code> database with <code>yardbird_user</code>, by updating the <code>connect_info</code> in:</p>

<pre><code>~/lib/Yardbird/lib/Yardbird/Model/DB.pm
</code></pre>

<p><a name='a306'></a></p>

<h3>Install Local Perl</h3>

<p>This is where the deployment began to get difficult.</p>

<p>The system Perl on the server is 5.10.1 and I had no luck installing modules with cPanel or local::lib using the system Perl. Besides, I like the idea of using a newer version of Perl so I installed it locally.</p>

<p>There are two things I needed <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a> to do for me to install a local perl:</p>

<ul>
<li>Enable "gcc" and "as" compiler access.</li>
<li>Resolve localhost in the /etc/hosts file, i.e. fix the issue that prevents '127.0.0.1' resolving.</li>
</ul>

<p>Then installing a local perl should be this easy:</p>

<pre><code>accountname@yardbirdfanclub.org [~]# mkdir perl5
accountname@yardbirdfanclub.org [~]# wget http://www.cpan.org/src/5.0/perl-5.16.2.tar.gz
accountname@yardbirdfanclub.org [~]# tar -xzf perl-5.16.2.tar.gz
accountname@yardbirdfanclub.org [~]# cd perl-5.16.2
accountname@yardbirdfanclub.org [~/perl-5.16.2]# ./Configure -des -Dprefix=$HOME/perl5
accountname@yardbirdfanclub.org [~/perl-5.16.2]# make
accountname@yardbirdfanclub.org [~/perl-5.16.2]# make test
accountname@yardbirdfanclub.org [~/perl-5.16.2]# make install
</code></pre>

<p>Without localhost resolved "make install" produces this error:</p>

<pre><code>lib/Net/hostent.t Failed test 'gethostbyaddr('127.0.0.1')'
</code></pre>

<p>The compiler access problem was easier to detect, I got the following error when compiling modules:</p>

<pre><code>cc: error trying to exec 'as': execvp: Permission denied
</code></pre>

<p><a name='a307'></a></p>

<h3>Install local::lib?</h3>

<p>This is where things got strange.</p>

<p>After installing a bootstrapped local::lib and cpanm, modules installed where I intended but @INC did not contain the correct paths to them. Maybe I did something wrong, I dunno. My solution was to explicitly set @INC in:</p>

<pre><code>~/lib/Yardbird/script/yardbird_fastcgi.fcgi
</code></pre>

<p>Reflecting back on this, I'm wondering if it was necessary to use local::lib since I successfully installed a local perl. I would research this before I would do it again.</p>

<p>See:</p>

<ul>
<li>Catalyst::Manual::Deployment::SharedHosting</li>
<li>local::lib</li>
</ul>

<p><a name='a308'></a></p>

<h3>Install App::cpanminus</h3>

<pre><code>accountname@yardbirdfanclub.org [~]# cpan App::cpanminus
</code></pre>

<p><a name='a309'></a></p>

<h3>Install Modules</h3>

<p>I suppose this is where my lack of skills as a Perl Newbie continues to show.</p>

<p>First I used cpanm to install these modules:</p>

<ul>
<li>Moose</li>
<li>Catalyst::Devel</li>
<li>DBIx::Class</li>
<li>HTML::FormHandler </li>
</ul>

<p>After those installed I started going to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with my browser then looked at the server error log in cPanel to determine what needed to be installed next. If nothing shows up in the server error log, try running yardbird_fastcgi.pl from ssh.</p>

<p>These are the modules/distributions I continued to install with cpanm:</p>

<ul>
<li>Catalyst::Plugin::Session::State::Cookie</li>
<li>Catalyst::Plugin::Session::Store::File</li>
<li>Catalyst::Authentication::Realm::SimpleDB</li>
<li>Catalyst::Plugin::Authorization::Roles</li>
<li>HTML::FormHandler::Model::DBIC</li>
<li>MooseX::MarkAsMethods</li>
<li>MooseX::NonMoose</li>
<li>DBIx::Class::TimeStamp</li>
<li>DBIx::Class::PassphraseColumn</li>
<li>FCGI</li>
<li>DBD::mysql</li>
</ul>

<p><a name='a310'></a></p>

<h3>\o/</h3>

<p>Finally, after installing DBD::mysql my Yardbird application appeared in my browser and I jumped for joy! I logged in as webmaster and everything worked, just like the Catalyst::Manual::Deployment::SharedHosting documentation said it would!</p>

<pre><code>username: webmaster
password: password
</code></pre>

<p><a name='a102'></a></p>

<h1>Other Deployment Options</h1>

<p><a name='a203'></a></p>

<h2>VPS</h2>

<p>I have little knowledge about deploying to Virtual Private Servers, but it would be great to have root access and all resources on the VPS dedicated and available to me and my sites. The disadvantage is cost, but it seems cost has dropped at <a href='http://asmallorange.com' target='_blank'>asmallorange.com</a> from what I recall a few months ago. At the time I was told:</p>

<pre><code>Most sites that run OK on a shared web hosting account will run OK on a 1024meg Cloud VPS.
</code></pre>

<p>Today they are advertising:</p>

<pre><code>Plan reflects FREE 2x RAM upgrade. New services only. 

Cores: 1
RAM: 1536MB
Storage: 15GB
Bandwidth: 600GB
Price: $25 a month, 2 months free with annual plan
</code></pre>

<p><a name='a204'></a></p>

<h2>dotCloud</h2>

<p>I know next to nothing about dotCloud, but after my first failed attempt to deploy <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> on shared hosting I had a conversation with folks at irc.perl.org that brought it to my attention. I was told:</p>

<ul>
<li>It is where you push your code and let them deal with the web hosting details.</li>
<li>dotcloud is nice up to sandbox, go live and it's spendy.</li>
</ul>

<p>See:</p>

<ul>
<li><a href='http://blogs.perl.org/users/phillip_smith/2011/08/dotcloud-loves-catalyst-apps-up-and-running-in-10-minutes-perl-in-the-cloud-part-iii.html' target='_blank'>dotCloud loves Catalyst apps</a> </li>
<li><a href='https://www.dotcloud.com/' target='_blank'>www.dotcloud.com</a> </li>
</ul>

<p><a name='a103'></a></p>

<h1>What's Next?</h1>

<p>Putting myself out there to the Perl community is scary.</p>

<p>Though I'm a musician and I too studied but did not complete a music degree, I'm no Larry Wall - though I would like to play some music with him!</p>

<p>Though I too am thankful for <a href='http://blogs.perl.org/users/peter_rabbitson/2013/05/on-the-awesomeness-of-the-perl-community.html' target='_blank'>the awesomeness of the Perl community</a> - I am no Peter Rabbitson, but I do use DBIx::Class.</p>

<p>Though I go by j0e since receiving his "heavy tools" via irc.perl.org a year or so ago - I am no t0m. I am no mst, hobbs, joel, jnap, kd, castaway, dpetrov, gshank, or any of the others who have generously helped me at irc.perl.org.</p>

<p>I don't have anywhere near the brains or technical skill of any of those mentioned, or most others from what I see in the blogs and other things I read. How likely will it be to find people willing to pay me to use and develop my skills? How will I ever give back to the Perl community?</p>

<p>Though I contributed these beginner-level <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> tutorials, I have contributed nothing compared to what others do. How can I belong to this community without the skills, without doing the work and making the contributions I see others making around me? Yet I do feel welcome to try, and I do desire to be a contributing member of this community. </p>

<p><a name='a205'></a></p>

<h2>Apply for Jobs</h2>

<p>My resume is weak and I'm a beginner, but I'm going to put myself out there for hire. This morning I read the <a href='http://hackerjobs.co.uk/jobs/2013/5/1/state51-perl-developer-music-and-metadata' target='_blank'>job posting</a>  in Gabor's Perlweekly and would be thrilled to have the opportunity to contribute to an effort like this, but I am not an "exceptional developer". I'll put myself out there and see what happens, and in the meantime do the best I can do.</p>

<p><a name='a206'></a></p>

<h2>Notes from a Newbie: YAPC::NA 2013, Austin, Texas</h2>

<p>When I registered for YAPC it was a big decision for me, for several reasons I won't bother to mention. I saw the call for speakers and punched in a proposal, with no illusions that I have knowledge any skilled Perl user would care about. But I figured few would be interested in giving a talk to beginners, so I went for it and my proposal was accepted. I know it is only a 20-minute talk, but I'm very pleased to be a speaker at this conference.</p>

<p>I hope some Newbies show up, they are who I want to address. My idea is to provide all attendees a very short survey consisting of only 4 or 5 questions, something like this:</p>

<pre><code>(Yes/No) Have you created and used Perl scripts?
(Yes/No) Have you used perldoc::server?
(Yes/No) Have you used irc.perl.org?
(Yes/No) Have you published any entries to blogs.perl.org?
(Yes/No) If you have not connected with anybody in the Perl community, would you like to do so?

If you are a veteran Perl user and would be willing to connect with a Newbie, please provide the following:

Name:
Location:
Email:

If you are a Newbie who would like to connect with a veteran Perl user, please provide the following:

Name:
Location:
Email:
</code></pre>

<p>The purpose of this survey will be to determine if any Newbies are present, and which of the survey items I may be able to help them with.</p>

<p>I am thinking I'll ask one of the veterans to be our "connection" to hook Newbies up. This person will have the responsibility of handing out surveys and pencils, collecting them, quickly tallying the results, reporting back to the group and hooking Newbies up. How about it <a href='http://blogs.perl.org/users/peter_rabbitson/' target='_blank'>Peter Rabbitson</a>? You do a lot already, will you help us out again?</p>

<p><a name='a207'></a></p>

<h2>Improve yardbirdfanclub.org?</h2>

<p>I have no specific plans for <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a>. It's purpose was for me to learn some basic skills and to share those skills with others, and I haven't given it much thought beyond that.</p>

<p>Perhaps I'll add a Flickr feed to the site, using AJAX and jQuery to display a photo album of Bird. I would like some video of Bird, his disc catching abilities and athleticism are incredible. But really, I may not do much of anything at all with the site. I think it mostly depends on how long it takes me to find a job using Perl. Once I am hired I will put everything I can into being successful at that job, and I imagine I won't have much time for <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a>. In the meantime I have <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> to develop my skills when I can find time. Maybe I'll continue to share what I learn, and maybe that will be my contribution to the community. It all depends on how the future unfolds, I'll need to find my way as I go.</p>

<p><a name='a104'></a></p>

<h1>Where to Find the Yardbird Source Code</h1>

<p>The Yardbird application, as I've developed it thus far in these tutorials, is available at GitHub:</p>

<ul>
<li><a href='https://github.com/j0eaxe/CatalystX-Examples' target='_blank'>https://github.com/j0eaxe/CatalystX-Examples</a></li>
</ul>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 15: Edit, Delete and View Members</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4565</id>

    <published>2013-04-14T05:04:29Z</published>
    <updated>2013-04-14T05:54:29Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a100'>Login</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a101'>Edit Members</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a102'>Create New Member: Improved</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a103'>Delete Members</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a104'>View Members</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a105'>Summary</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a200'>Yardbird/lib/Yardbird/Controller/Member.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a201'>Yardbird/root/src/member/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a202'>Yardbird/root/src/member/edit.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a203'>Yardbird/root/src/member/create.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a204'>Yardbird/root/src/member/delete.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a205'>Yardbird/root/src/member/show.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a206'>Yardbird/root/src/header.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a207'>Yardbird/root/static/css/main.css</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a208'>Yardbird/lib/Yardbird/Schema/Result/User.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html#a209'>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm</a></li>
</ul>

<p></ul></p>
]]>
        <![CDATA[<p><a name='a100'></a> </p>

<h1>Login</h1>

<p>Before going any further you should probably go to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a>, login and test-drive the application's "Members" features. Doing so will help you more easily understand what we will be doing.</p>

<p>Currently your Yardbird application provides no way for members to edit their membership information, cancel their membership, or to view other member's information. The first step we will take to add these features will be to display a "Members" link in the main menu at the top of the page when users login. </p>

<p>Replace the "Members" item in your header.tt template with this:</p>

<pre><code>[% IF c.user_exists %] 
&lt;li&gt;&lt;a href="[% c.uri_for_action('/member/index') %]"&gt;Members&lt;/a&gt;&lt;/li&gt;
[% END %]
</code></pre>

<p>After doing so you should have this:</p>

<p>Yardbird/root/src/header.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('blog/index') %]"&gt;Blog&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/member/index') %]"&gt;Members&lt;/a&gt;&lt;/li&gt;
          [% END %] 
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;[% c.user.name %] | &lt;a href="[% c.uri_for_action('/logout/index') %]"&gt;Logout&lt;/a&gt;&lt;/li&gt;
          [% ELSE %]
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/li&gt; 
          [% END %]
        &lt;/div&gt;  
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p>Next we will edit our controller action that we are linking to:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [sort_users_by_name($self, $c)]);

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Recall that we display Member's names in a rather complicated manner, using a Result class method to return a username, firstname, or firstname and lastname, depending on what info members provided for themselves. Without this complexity it would be much easier to use the "order_by" attribute and search in our ResultSet class (as we've done previously and will soon do again) rather than writing our own sort routine.</p>

<p>See:</p>

<ul>
<li>DBIx::Class::ResultSet - ATTRIBUTES - order_by</li>
</ul>

<p>Since we're using the complicated process described above, we need to create a routine to sort members by name:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub sort_users_by_name {
  my ( $self, $c ) = @_;

  my @users;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    push @users, { id =&gt; $user-&gt;id, name =&gt; $user-&gt;name };
  }

  my @sorted_users = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @users;

  my @rows;
  for my $user (@sorted_users) {
    push @rows, $c-&gt;model('DB::User')-&gt;specific($user-&gt;{id})-&gt;first;
  }

  return @rows;
}
</code></pre>

<p>We add the new search method we are using in our sort routine above to our ResultSet class:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm:</p>

<pre><code>sub specific {
  my ($self, $uID) = @_;

  return $self-&gt;search({id =&gt; $uID});
}
</code></pre>

<p>Finally, we will create the template for our Member.pm controller index action. As we proceed, we will add links to edit, delete and view member info, but for now let's just show the sorted member's names in a sidebar menu:</p>

<p>Yardbird/root/src/member/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.name %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
  &lt;h3&gt;Members&lt;/h3&gt;
  &lt;p&gt;You may update your personal membership information and view other member's information.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application:</p>

<pre><code>Yardbird]$ script/yardbird_server.pl -d -r
</code></pre>

<p>You should now get a "Members" link in the main menu at the top of the page when you login:</p>

<p><img alt="yfc15a1.png" src="http://blogs.perl.org/users/j0e/yfc15a1.png" width="600" height="192" class="mt-image-none" style="" /></p>

<p>The "Members" link should now be active and give you a sidebar menu with member names sorted:</p>

<p><img alt="yfc15a2.png" src="http://blogs.perl.org/users/j0e/yfc15a2.png" width="600" height="275" class="mt-image-none" style="" /></p>

<p><a name='a101'></a> </p>

<h1>Edit Members</h1>

<p>We will start by creating a new edit action for our Member.pm controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub edit :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user entered a malicious uri and is trying to edit another user's info.
  $c-&gt;detach('/unauthorized_action') if $c-&gt;user-&gt;id != $uID;

  # Sort users by username and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [$c-&gt;model('DB::User')-&gt;all_username]);

  # Prepare to use the form.
  $c-&gt;stash(template =&gt; 'member/edit.tt', form =&gt; $self-&gt;form );

  # Validate and update user row in user table.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $uID, 
    params =&gt; $c-&gt;req-&gt;params,
    schema =&gt; $c-&gt;model('DB')-&gt;schema,
  );

  # Redirect to Members page.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/member/index'));
}
</code></pre>

<p>If you compare our new edit method to our create method in the Member.pm controller, you won't find anything we haven't already done. Yes, I'm not consistent with my Perl identifiers, i.e. my parameters $user_id and $uID are not consistent - but please forgive me for this. I would fix it if this weren't a series of tutorials, but I don't want to confuse anybody by changing what we've already done.</p>

<p>Notice in our new edit method we are putting a list of users in the stash. I do this so we may display a sidebar menu of usernames, for members to see the ones that have already been used.</p>

<p>You need to add the new search method we use above to our ResultSet Class:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm:</p>

<pre><code>sub all_username {
  my ($self, $uID) = @_;

  return $self-&gt;search({}, {order_by =&gt; ['username']});
}
</code></pre>

<p>Notice how much easier it is to sort by a table column rather than a Result Class Row method?</p>

<p>We need to create a template for our new edit action, which is almost identical to the template we created to create new members:</p>

<p>Yardbird/root/src/member/edit.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Usernames&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.username %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Edit Your Membership Information&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/edit', c.user.id) %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit" /&gt;
    &lt;/form&gt; 

    &lt;div class="page_divider_with_sidebar"&gt;&lt;/div&gt;
    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>I added a new div tag to this template because I decided the page could be improved with a horizontal line dividing the form from the info under it, therefore you need to add a new style to your stylesheet:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.page_divider_with_sidebar {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.3em;
  margin-bottom: 1.3em;
}
</code></pre>

<p>Finally, let's create a new sidebar menu in our index.tt template with a link to edit member info:</p>

<pre><code>&lt;div class="sidebar_item"&gt;
  &lt;div class="sidebar_item_content"&gt;
    &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
    &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>This is what you should have after adding the above code:</p>

<p>Yardbird/root/src/member/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.name %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
  &lt;h3&gt;Members&lt;/h3&gt;
  &lt;p&gt;You may update your personal membership information and view other member's information.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application and you should get a sidebar menu with a link to edit member info:</p>

<p><img alt="yfc15b1.png" src="http://blogs.perl.org/users/j0e/yfc15b1.png" width="600" height="334" class="mt-image-none" style="" /></p>

<p>You should also be able to edit member info:</p>

<p><img alt="yfc15b2.png" src="http://blogs.perl.org/users/j0e/yfc15b2.png" width="600" height="469" class="mt-image-none" style="" /></p>

<p><a name='a102'></a> </p>

<h1>Create New Member: Improved</h1>

<p>If you would try to create a new member you would find some differences between the page to create and the one we just implemented to edit member info. When we create members, we don't have the sidebar menu of usernames, or the horizontal line under the form that we added to our edit page. Let's add these features, and automatically login the new user after their membership is created.</p>

<p>We will start with our controller, this is what we now have:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c-&gt;model('DB::User')-&gt;most_recent-&gt;first; 
  my $userid = $row-&gt;id; 
  my $name = $row-&gt;name; 

  # Create new member's first blog entry.
  $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
  $row-&gt;userid($userid);
  $row-&gt;title("Welcome ".$name."!");
  $row-&gt;content(
"&lt;h3&gt;You Are Now A Member Of The YARDBIRD Fan Club!&lt;/h3&gt;
&lt;p&gt;You may now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post blog entries and comments via the Blog pages.&lt;/li&gt;
&lt;li&gt;Optionally provide your contact and other information to other users via the Members pages.&lt;/li&gt;
&lt;li&gt;View other member's contact information via the Members pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About This Blog Entry&lt;/h3&gt;
&lt;p&gt;This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.&lt;/p&gt;
&lt;p&gt;It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;"
);
  $row-&gt;created(DateTime-&gt;now);
  $row-&gt;insert; 

  # Return to homepage.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p>We will first put a list of users sorted by username in the stash as we do when we edit member info:</p>

<pre><code># Sort users by username and put in stash for template sidebar menu.
$c-&gt;stash(users =&gt; [$c-&gt;model('DB::User')-&gt;all_username]);
</code></pre>

<p>Then we will login the new user after auto-generating their first blog entry, and redirect to their blog entries page:</p>

<pre><code># Automatically login new user.
$c-&gt;authenticate({ username =&gt; $self-&gt;form-&gt;value-&gt;{username}, password =&gt; $self-&gt;form-&gt;value-&gt;{password} });

# Redirect to new user's blog entries page.
$c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
</code></pre>

<p>This is what you should have after adding the above changes:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  # Sort users by username and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [$c-&gt;model('DB::User')-&gt;all_username]);

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c-&gt;model('DB::User')-&gt;most_recent-&gt;first; 
  my $userid = $row-&gt;id; 
  my $name = $row-&gt;name; 

  # Create new member's first blog entry.
  $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
  $row-&gt;userid($userid);
  $row-&gt;title("Welcome ".$name."!");
  $row-&gt;content(
"&lt;h3&gt;You Are Now A Member Of The YARDBIRD Fan Club!&lt;/h3&gt;
&lt;p&gt;You may now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post blog entries and comments via the Blog pages.&lt;/li&gt;
&lt;li&gt;Optionally provide your contact and other information to other users via the Members pages.&lt;/li&gt;
&lt;li&gt;View other member's contact information via the Members pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About This Blog Entry&lt;/h3&gt;
&lt;p&gt;This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.&lt;/p&gt;
&lt;p&gt;It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;"
);
  $row-&gt;created(DateTime-&gt;now);
  $row-&gt;insert; 

  # Automatically login new user.
  $c-&gt;authenticate({ username =&gt; $self-&gt;form-&gt;value-&gt;{username}, password =&gt; $self-&gt;form-&gt;value-&gt;{password} });

  # Redirect to new user's blog entries page.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
}
</code></pre>

<p>We need to add the sidebar menu and horizontal bar to the corresponding template. This is what you should have after doing so:</p>

<p>Yardbird/root/src/member/create.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Usernames&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.username %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Join The YARDBIRD Fan Club&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/&gt;
    &lt;/form&gt; 

    &lt;div class="page_divider_with_sidebar"&gt;&lt;/div&gt;
    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application and the page to create new members should be consistent with the one to edit members, and the new user should be automatically logged-in and redirected to view their first blog entry after creating their membership:</p>

<p><img alt="yfc15c1.png" src="http://blogs.perl.org/users/j0e/yfc15c1.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><img alt="yfc15c2.png" src="http://blogs.perl.org/users/j0e/yfc15c2.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><img alt="yfc15c3.png" src="http://blogs.perl.org/users/j0e/yfc15c3.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a103'></a></p>

<h1>Delete Members</h1>

<p>We need to create a controller action:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub delete :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user entered a malicious uri and is trying to delete another user's membership.
  $c-&gt;detach('/unauthorized_action') if $c-&gt;user-&gt;id != $uID;

  my $submit = $c-&gt;req-&gt;params;
  if ($submit-&gt;{submit} eq 'Yes') {
    $c-&gt;model('DB::User')-&gt;specific($uID)-&gt;delete; 
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/logout/index'));
  }
  elsif ($submit-&gt;{submit} eq 'No') {
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('member/index'));
  }

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Since both the <code>blog</code> and <code>blog_comment</code> tables have foreign keys that reference the <code>user</code> table and cascade delete, all blog entries and comments belonging to a deleted user are automatically deleted when that user is deleted. Since the <code>blog_comment</code> table also has a foreign key that references the <code>blog</code> table and cascade deletes, all blog comments made by others for a deleted blog entry are also deleted when that blog entry is deleted.</p>

<p>So when a user is deleted, that causes all blog entries and comments belonging to the deleted user to be automatically deleted, and when one of those blog entries has comments somebody else has made for it - those comments are deleted too!</p>

<p>Dung Beetle -> Dung Beetle dung -> Dung Beetle Dung Beetle ->  Dung Beetle Dung Beetle dung -> Dung Beetle Dung Beetle Dung Beetle -> ...</p>

<p>Create a corresponding template:</p>

<p>Yardbird/root/src/member/delete.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Cancel Membership&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Canceling your membership permanently deletes:
          &lt;ul&gt;
          &lt;li&gt;All your membership information&lt;/li&gt;
          &lt;li&gt;All your blog entries&lt;/li&gt;
          &lt;li&gt;All your comments on yours and other's blog entries&lt;/li&gt;
          &lt;li&gt;All comments on your blog entries&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;tr&gt;
        &lt;td&gt;Do you really want to cancel your membership?&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Add a link to our sidebar menu:</p>

<pre><code>&lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;Cancel Membership&lt;/a&gt;&lt;/p&gt;
</code></pre>

<p>After adding the link you should have:</p>

<p>Yardbird/root/src/member/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;Cancel Membership&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.name %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
  &lt;h3&gt;Members&lt;/h3&gt;
  &lt;p&gt;You may update your personal membership information and view other member's information.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run it and you should be able to delete memberships:</p>

<p><img alt="yfc15d1.png" src="http://blogs.perl.org/users/j0e/yfc15d1.png" width="600" height="390" class="mt-image-none" style="" /></p>

<p><img alt="yfc15d2.png" src="http://blogs.perl.org/users/j0e/yfc15d2.png" width="600" height="330" class="mt-image-none" style="" /></p>

<p>Don't forget to verify that blog entries and comments belonging to the deleted user, as well as comments (by others) for the deleted user's blog entries are all deleted too.</p>

<p><a name='a104'></a> </p>

<h1>View Members</h1>

<p>Let's start with the controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub show :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [sort_users_by_name($self, $c)]);

  $c-&gt;stash(row =&gt; $c-&gt;model('DB::User')-&gt;specific($uID)-&gt;first);

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Create the corresponding template:</p>

<p>Yardbird/root/src/member/show.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;Cancel Membership&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/show', user.id) %]"&gt;[% user.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p&gt;
      &lt;span class="label10"&gt;Username:&lt;/span&gt;[% row.username %]&lt;br&gt;
      [% IF row.has_name %]
        &lt;span class="label10"&gt;Name:&lt;/span&gt;[% row.name %]&lt;br&gt;
      [% END %]
      [% IF row.email_visible %]
        &lt;span class="label10"&gt;Email:&lt;/span&gt;[% row.email %]&lt;br&gt;
      [% END %]
      [% IF row.location %]
        &lt;span class="label10"&gt;Location:&lt;/span&gt;[% row.location %]&lt;br&gt;
      [% END %]
      [% IF row.about_me %]
        &lt;p&gt;[% row.about_me %]&lt;/p&gt;
      [% END %]
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Notice that our new template needs a new style:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.label10 {
  float: left;
  width: 5.5em;
}
</code></pre>

<p>It also needs a new Row method in our Result class:</p>

<p>Yardbird/lib/Yardbird/Schema/Result/User.pm:</p>

<pre><code>sub has_name {
  my ($self) = @_;

  if ($self-&gt;firstname) {
    return 1;
  }
  elsif ($self-&gt;lastname) {
    return 1;
  }
  else {
    return 0;
  }
}
</code></pre>

<p>Finally, we can activate a link in our index.tt template sidebar menu to view member's info. Currently we have this:</p>

<p>Yardbird/root/src/member/index.tt:</p>

<pre><code>&lt;div class="sidebar_item"&gt;
  &lt;div class="sidebar_item_content"&gt;
    &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
    [% FOREACH user IN users %]
      &lt;p class="sidebar_item_title"&gt;[% user.name %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>We need to change it to this:</p>

<p>Yardbird/root/src/member/index.tt:</p>

<pre><code>&lt;div class="sidebar_item"&gt;
  &lt;div class="sidebar_item_content"&gt;
    &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
    [% FOREACH user IN users %]
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/show', user.id) %]"&gt;[% user.name %]&lt;/a&gt;&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>Run the application and you should be able to view member info:</p>

<p><img alt="yfc15e1.png" src="http://blogs.perl.org/users/j0e/yfc15e1.png" width="600" height="371" class="mt-image-none" style="" /></p>

<p><img alt="yfc15e2.png" src="http://blogs.perl.org/users/j0e/yfc15e2.png" width="600" height="371" class="mt-image-none" style="" /></p>

<p><a name='a105'></a> </p>

<h1>Summary</h1>

<p><a name='a200'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Member.pm</h2>

<pre><code>package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;
use Yardbird::Form::User; 

BEGIN { extends 'Catalyst::Controller'; }

has 'form' =&gt; (
  isa =&gt; 'Yardbird::Form::User',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::User-&gt;new }
);

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [sort_users_by_name($self, $c)]);

  $c-&gt;detach($c-&gt;view("TT"));
}

sub about :Local :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  # Sort users by username and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [$c-&gt;model('DB::User')-&gt;all_username]);

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c-&gt;model('DB::User')-&gt;most_recent-&gt;first; 
  my $userid = $row-&gt;id; 
  my $name = $row-&gt;name; 

  # Create new member's first blog entry.
  $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
  $row-&gt;userid($userid);
  $row-&gt;title("Welcome ".$name."!");
  $row-&gt;content(
"&lt;h3&gt;You Are Now A Member Of The YARDBIRD Fan Club!&lt;/h3&gt;
&lt;p&gt;You may now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post blog entries and comments via the Blog pages.&lt;/li&gt;
&lt;li&gt;Optionally provide your contact and other information to other users via the Members pages.&lt;/li&gt;
&lt;li&gt;View other member's contact information via the Members pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About This Blog Entry&lt;/h3&gt;
&lt;p&gt;This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.&lt;/p&gt;
&lt;p&gt;It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;"
);
  $row-&gt;created(DateTime-&gt;now);
  $row-&gt;insert; 

  # Automatically login new user.
  $c-&gt;authenticate({ username =&gt; $self-&gt;form-&gt;value-&gt;{username}, password =&gt; $self-&gt;form-&gt;value-&gt;{password} });

  # Redirect to new user's blog entries page.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
}

sub edit :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user entered a malicious uri and is trying to edit another user's info.
  $c-&gt;detach('/unauthorized_action') if $c-&gt;user-&gt;id != $uID;

  # Sort users by username and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [$c-&gt;model('DB::User')-&gt;all_username]);

  # Prepare to use the form.
  $c-&gt;stash(template =&gt; 'member/edit.tt', form =&gt; $self-&gt;form );

  # Validate and update user row in user table.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $uID, 
    params =&gt; $c-&gt;req-&gt;params,
    schema =&gt; $c-&gt;model('DB')-&gt;schema,
  );

  # Redirect to Members page.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/member/index'));
}

sub delete :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user entered a malicious uri and is trying to delete another user's membership.
  $c-&gt;detach('/unauthorized_action') if $c-&gt;user-&gt;id != $uID;

  my $submit = $c-&gt;req-&gt;params;
  if ($submit-&gt;{submit} eq 'Yes') {
    $c-&gt;model('DB::User')-&gt;specific($uID)-&gt;delete; 
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/logout/index'));
  }
  elsif ($submit-&gt;{submit} eq 'No') {
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('member/index'));
  }

  $c-&gt;detach($c-&gt;view("TT"));
}

sub show :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c-&gt;stash(users =&gt; [sort_users_by_name($self, $c)]);

  $c-&gt;stash(row =&gt; $c-&gt;model('DB::User')-&gt;specific($uID)-&gt;first);

  $c-&gt;detach($c-&gt;view("TT"));
}

sub sort_users_by_name {
  my ( $self, $c ) = @_;

  my @users;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    push @users, { id =&gt; $user-&gt;id, name =&gt; $user-&gt;name };
  }

  my @sorted_users = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @users;

  my @rows;
  for my $user (@sorted_users) {
    push @rows, $c-&gt;model('DB::User')-&gt;specific($user-&gt;{id})-&gt;first;
  }

  return @rows;
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a201'></a> </p>

<h2>Yardbird/root/src/member/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;Cancel Membership&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/show', user.id) %]"&gt;[% user.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
  &lt;h3&gt;Members&lt;/h3&gt;
  &lt;p&gt;You may update your personal membership information and view other member's information.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a202'></a> </p>

<h2>Yardbird/root/src/member/edit.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Usernames&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.username %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Edit Your Membership Information&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/edit', c.user.id) %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit" /&gt;
    &lt;/form&gt; 

    &lt;div class="page_divider_with_sidebar"&gt;&lt;/div&gt;
    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a203'></a> </p>

<h2>Yardbird/root/src/member/create.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Usernames&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;[% user.username %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Join The YARDBIRD Fan Club&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/&gt;
    &lt;/form&gt; 

    &lt;div class="page_divider_with_sidebar"&gt;&lt;/div&gt;
    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a204'></a> </p>

<h2>Yardbird/root/src/member/delete.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Cancel Membership&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Canceling your membership permanently deletes:
          &lt;ul&gt;
          &lt;li&gt;All your membership information&lt;/li&gt;
          &lt;li&gt;All your blog entries&lt;/li&gt;
          &lt;li&gt;All your comments on yours and other's blog entries&lt;/li&gt;
          &lt;li&gt;All comments on your blog entries&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;tr&gt;
        &lt;td&gt;Do you really want to cancel your membership?&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a205'></a> </p>

<h2>Yardbird/root/src/member/show.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;[% c.user.name %]&lt;/strong&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/edit', c.user.id) %]"&gt;Edit Membership Info&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/delete', c.user.id) %]"&gt;Cancel Membership&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Members&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH user IN users %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/member/show', user.id) %]"&gt;[% user.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p&gt;
      &lt;span class="label10"&gt;Username:&lt;/span&gt;[% row.username %]&lt;br&gt;
      [% IF row.has_name %]
        &lt;span class="label10"&gt;Name:&lt;/span&gt;[% row.name %]&lt;br&gt;
      [% END %]
      [% IF row.email_visible %]
        &lt;span class="label10"&gt;Email:&lt;/span&gt;[% row.email %]&lt;br&gt;
      [% END %]
      [% IF row.location %]
        &lt;span class="label10"&gt;Location:&lt;/span&gt;[% row.location %]&lt;br&gt;
      [% END %]
      [% IF row.about_me %]
        &lt;p&gt;[% row.about_me %]&lt;/p&gt;
      [% END %]
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a206'></a> </p>

<h2>Yardbird/root/src/header.tt</h2>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('blog/index') %]"&gt;Blog&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/member/index') %]"&gt;Members&lt;/a&gt;&lt;/li&gt;
          [% END %] 
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;[% c.user.name %] | &lt;a href="[% c.uri_for_action('/logout/index') %]"&gt;Logout&lt;/a&gt;&lt;/li&gt;
          [% ELSE %]
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/li&gt; 
          [% END %]
        &lt;/div&gt;  
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p><a name='a207'></a> </p>

<h2>Yardbird/root/static/css/main.css</h2>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}

#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}

.content_title {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0.3em;
}

.content_subtitle {
  font-size: 0.8em;
  margin-top: -1.6em;
}

.content_title a {
  color: #004080;
  text-decoration: none; 
}

.content_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_title {
  font-size: 0.9em;
  font-weight: bold;
}

.sidebar_item_title a {
  color: #004080;
  text-decoration: none; 
}

.sidebar_item_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_subtitle {
  font-size: 0.65em;
  margin-top: -1.5em;
}

.sidebar_item_sub_subtitle {
  font-size: 0.65em;
  margin-top: -1.0em;
}

.bottom_of_content {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.5em;
  margin-bottom: 1.3em;
}

.top_of_comments {
  margin-top: 1.3em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.between_comments {
  margin-top: -0.6em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.bottom_of_comments {
  border-bottom: 2px solid #87ceeb;
  margin-top: -0.6em;
  margin-bottom: 1.3em;
}

.comment_content {
  margin-bottom: 1em;
}

.comment_subtitle {
  font-size: 0.7em;
  color: #004080; 
  margin-top: 1.2em;
}

.page_divider_with_sidebar {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.3em;
  margin-bottom: 1.3em;
}

.label10 {
  float: left;
  width: 5.5em;
}
</code></pre>

<p><a name='a208'></a> </p>

<h2>Yardbird/lib/Yardbird/Schema/Result/User.pm</h2>

<pre><code># DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:s1r1mjXJqZzRdLk0PGpC9A


# You can replace this text with custom code or comments, and it will be preserved on regeneration

# Have the 'password' column use a SHA-1 hash and 20-byte salt
# with RFC 2307 encoding; Generate the 'check_password" method
__PACKAGE__-&gt;add_columns(
    'password' =&gt; {
        passphrase       =&gt; 'rfc2307',
        passphrase_class =&gt; 'SaltedDigest',
        passphrase_args  =&gt; {
            algorithm   =&gt; 'SHA-1',
            salt_random =&gt; 20.
        },
        passphrase_check_method =&gt; 'check_password',
    },
); 

sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}

sub has_name {
  my ($self) = @_;

  if ($self-&gt;firstname) {
    return 1;
  }
  elsif ($self-&gt;lastname) {
    return 1;
  }
  else {
    return 0;
  }
}

__PACKAGE__-&gt;meta-&gt;make_immutable;
1;
</code></pre>

<p><a name='a209'></a> </p>

<h2>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm</h2>

<pre><code>package Yardbird::Schema::ResultSet::User;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, { order_by =&gt; {-desc =&gt; ['id']} });
} 

sub specific {
  my ($self, $uID) = @_;

  return $self-&gt;search({id =&gt; $uID});
} 

sub all_username {
  my ($self, $uID) = @_;

  return $self-&gt;search({}, {order_by =&gt; ['username']});
} 

1;
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 14: Edit and Delete Comments</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4504</id>

    <published>2013-04-07T05:16:49Z</published>
    <updated>2013-04-07T05:36:40Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html#a100'>Edit and Delete Comments</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html#a101'>Summary</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html#a200'>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html#a201'>Yardbird/root/src/blog/entries/edit_comments.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html#a202'>Yardbird/root/src/blog/entries/index.tt</a></li>
</ul>

<p></ul></p>
]]>
        <![CDATA[<p><a name='a100'></a></p>

<h1>Edit and Delete Comments</h1>

<p>Before going any further you may want to go to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> to see how to edit and delete blog comments in the application. Doing so may make it easier to understand what we will be doing.</p>

<p>We will create a Blog/Entries.pm controller action to both edit and delete blog comments:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub edit_comments :Path('/blog/comments/edit') :Args(1) {
  my ($self, $c, $uID) = @_;

  # Detach if user is not logged-in, or is trying to delete somebody else's comments.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $uID);

  # blog/entries/index.tt only makes the "Edit/Delete Comments" link available if user
  # has comments. The only way a user would get this far into this subroutine if they
  # had no comments would be if they entered a malicious uri, so detach if that occurs.
  my @rows = $c-&gt;model('DB::BlogComment')-&gt;all_user($uID);
  $c-&gt;detach('/unauthorized_action') if !@rows;

  $c-&gt;stash(blog_comments =&gt; [@rows]);

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    my $rs = $c-&gt;model('DB::BlogComment')-&gt;all_user($uID);
    my $cnt = 0;
    for my $row (@rows) {
      if ($c-&gt;req-&gt;params-&gt;{'checkbox'.$cnt} eq "Yes") {
        $rs-&gt;specific_comment($row-&gt;id)-&gt;delete_all;
      }
      else {
        $row-&gt;content($c-&gt;req-&gt;params-&gt;{'content'.$cnt});
        $row-&gt;update;
      }
      $cnt++;
    }
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    my $row = $c-&gt;model('DB::Blog')-&gt;all_user($uID)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}
</code></pre>

<p>The Path attribute in my controller action above allows me to map it to the explicit URI: '/blog/comments/edit'. I do this because I want the URI for this action to be consistent with the URI's to create, edit and delete blog entries, i.e. '/blog/entries/create', '/blog/entries/edit' and '/blog/entries/delete'. In other words, I don't want the path to this action to be '/blog/edit_comments', I want it to be '/blog/comments/edit'.</p>

<p>See:</p>

<ul>
<li>Catalyst::Manual::Tutorial::03_MoreCatalystBasics </li>
</ul>

<p>Create the corresponding template:</p>

<p>Yardbird/root/src/blog/entries/edit_comments.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_comments.0.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_comments.0.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Edit/Delete Comments&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/edit_comments', blog_comments.0.userid.id) %]"&gt;
    &lt;table&gt;

      [% cnt = 0 %]
      [% FOREACH blog_comment IN blog_comments %]
        &lt;tr&gt;
          &lt;td&gt;&lt;textarea name="[% 'content' _ cnt %]" rows="10" cols="88" wrap="virtual"&gt;[% blog_comment.content | html %]&lt;/textarea&gt;&lt;/td&gt;
          &lt;td&gt;&lt;input type="checkbox" name="[% 'checkbox' _ cnt %]" value="Yes"/&gt;Delete&lt;/td&gt;
        &lt;/tr&gt;
        [% cnt = cnt + 1 %]
      [% END %]

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to edit/delete these comments?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>The link to edit and delete comments should appear in the blog/entries sidebar menu:</p>

<p>1) When a logged-in user with comments is viewing their own blog entries in the blog/entries page.</p>

<p>2) When a logged-in user with comments has no blog entries of their own and is viewing any blog/entries page.</p>

<p>If the above explanation is confusing, perhaps reviewing <a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html' target='_blank'>Notes from a Newbie 13: Create, Edit and Delete Blog Entries</a> and using <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> may help clear this up.</p>

<p>This is the link we will put in our sidebar menu:</p>

<pre><code>[% blog_comments_rs = blog_comments.all_user(c.user.id) %]
[% IF blog_comments_rs %]
  &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit_comments', c.user.id) %]"&gt;Edit/Delete Comments&lt;/a&gt;&lt;/p&gt;
[% END %]
</code></pre>

<p>This is what we now have in the sidebar menu:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt; 
    &lt;/div&gt;
  &lt;/div&gt;
[% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% END %]
</code></pre>

<p>This is what we should have after adding the links:</p>

<pre><code>[% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt; 

      [% blog_comments_rs = blog_comments.all_user(c.user.id) %]
      [% IF blog_comments_rs %]
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit_comments', c.user.id) %]"&gt;Edit/Delete Comments&lt;/a&gt;&lt;/p&gt;
      [% END %]

    &lt;/div&gt;
  &lt;/div&gt;
[% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;

      [% blog_comments_rs = blog_comments.all_user(c.user.id) %]
      [% IF blog_comments_rs %]
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit_comments', c.user.id) %]"&gt;Edit/Delete Comments&lt;/a&gt;&lt;/p&gt;
      [% END %]

    &lt;/div&gt;
  &lt;/div&gt;
[% END %]
</code></pre>

<p>Run the application:</p>

<pre><code>Yardbird]$ script/yardbird_server.pl -d -r
</code></pre>

<p>Logged-in users with comments should be able to edit and delete their comments when viewing their own blog entries in the blog/entries page:</p>

<p><img alt="yfc14a1.png" src="http://blogs.perl.org/users/j0e/yfc14a1.png" width="600" height="412" class="mt-image-none" style="" /></p>

<p><img alt="yfc14a2.png" src="http://blogs.perl.org/users/j0e/yfc14a2.png" width="600" height="350" class="mt-image-none" style="" /></p>

<p>Logged-in users with comments that have no blog entries of their own should be able to edit and delete their comments when viewing any blog/entries page:</p>

<p><img alt="yfc14a3.png" src="http://blogs.perl.org/users/j0e/yfc14a3.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a101'></a></p>

<h1>Summary</h1>

<p><a name='a200'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</h2>

<pre><code>package Yardbird::Controller::Blog::Entries;
use Moose;
use namespace::autoclean;
use Yardbird::Form::BlogComment;

BEGIN { extends 'Catalyst::Controller'; }

has 'comment_form' =&gt; (
  isa =&gt; 'Yardbird::Form::BlogComment',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::BlogComment-&gt;new }
); 

sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  # Determine if logged-in user has created any blog entries and put
  # flag in stash. The template uses this to allow user to create
  # first blog entry if they have none.
  if ($c-&gt;user_exists) {
    if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id) {
      $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first;
      if ($row) {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 0);
      }
      else {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 1);
      }
    }
  }

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}

sub create :Local :Args(0) {
  my ($self, $c) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  my $row;

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;insert;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}

sub edit :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Prepare to edit blog entry.
  # Detach if user is attempting to edit an entry that doesn't belong to user.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;update;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}

sub delete :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user attempts to delete an entry that doesn't belong to them.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq 'Yes') {
    $row-&gt;delete; 

    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
  elsif ($submit eq 'No') {
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
}

sub edit_comments :Path('/blog/comments/edit') :Args(1) {
  my ($self, $c, $uID) = @_;

  # Detach if user is not logged-in, or is trying to delete somebody else's comments.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $uID);

  # blog/entries/index.tt only makes the "Edit/Delete Comments" link available if user
  # has comments. The only way a user would get this far into this subroutine if they
  # had no comments would be if they entered a malicious uri, so detach if that occurs.
  my @rows = $c-&gt;model('DB::BlogComment')-&gt;all_user($uID);
  $c-&gt;detach('/unauthorized_action') if !@rows;

  $c-&gt;stash(blog_comments =&gt; [@rows]);

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    my $rs = $c-&gt;model('DB::BlogComment')-&gt;all_user($uID);
    my $cnt = 0;
    for my $row (@rows) {
      if ($c-&gt;req-&gt;params-&gt;{'checkbox'.$cnt} eq "Yes") {
        $rs-&gt;specific_comment($row-&gt;id)-&gt;delete_all;
      }
      else {
        $row-&gt;content($c-&gt;req-&gt;params-&gt;{'content'.$cnt});
        $row-&gt;update;
      }
      $cnt++;
    }
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    my $row = $c-&gt;model('DB::Blog')-&gt;all_user($uID)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a201'></a> </p>

<h2>Yardbird/root/src/blog/entries/edit_comments.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_comments.0.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_comments.0.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Edit/Delete Comments&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/edit_comments', blog_comments.0.userid.id) %]"&gt;
    &lt;table&gt;

      [% cnt = 0 %]
      [% FOREACH blog_comment IN blog_comments %]
        &lt;tr&gt;
          &lt;td&gt;&lt;textarea name="[% 'content' _ cnt %]" rows="10" cols="88" wrap="virtual"&gt;[% blog_comment.content | html %]&lt;/textarea&gt;&lt;/td&gt;
          &lt;td&gt;&lt;input type="checkbox" name="[% 'checkbox' _ cnt %]" value="Yes"/&gt;Delete&lt;/td&gt;
        &lt;/tr&gt;
        [% cnt = cnt + 1 %]
      [% END %]

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to edit/delete these comments?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a202'></a> </p>

<h2>Yardbird/root/src/blog/entries/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    [% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt; 

          [% blog_comments_rs = blog_comments.all_user(c.user.id) %]
          [% IF blog_comments_rs %]
            &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit_comments', c.user.id) %]"&gt;Edit/Delete Comments&lt;/a&gt;&lt;/p&gt;
          [% END %]

        &lt;/div&gt;
      &lt;/div&gt;
    [% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;

          [% blog_comments_rs = blog_comments.all_user(c.user.id) %]
          [% IF blog_comments_rs %]
            &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit_comments', c.user.id) %]"&gt;Edit/Delete Comments&lt;/a&gt;&lt;/p&gt;
          [% END %]

        &lt;/div&gt;
      &lt;/div&gt;
    [% END %]

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
    [% END %]

    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 13: Create, Edit and Delete Blog Entries</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4503</id>

    <published>2013-04-06T21:05:57Z</published>
    <updated>2013-04-06T21:45:36Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a100'>User Interface</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a200'>Implementation</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a201'>Reflection</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a101'>Create New Blog Entry</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a202'>Create Sidebar Menu</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a203'>Create Controller Actions and Templates</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a300'>unauthorized_action</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a301'>create</a></li>
</ul>

<p></ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a102'>Edit Blog Entry</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a103'>Delete Blog Entry</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a104'>Auto-Generate Blog Entry</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a105'>Summary</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a204'>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a205'>Yardbird/root/src/blog/entries/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a206'>Yardbird/root/src/blog/entries/create.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a207'>Yardbird/root/src/blog/entries/edit.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a208'>Yardbird/root/src/blog/entries/delete.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a209'>Yardbird/lib/Yardbird/Controller/Root.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a210'>Yardbird/root/src/unauthorized_action.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a211'>Yardbird/lib/Yardbird/Controller/Member.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html#a212'>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm</a></li>
</ul>

<p></ul></p>
]]>
        <![CDATA[<p><a name='a100'></a></p>

<h1>User Interface</h1>

<p>Currently, the only way to create new blog entries is with AutoCRUD. This is okay for development purposes, but not for a production application. We need to give users the ability to create, edit and delete blog entries without using AutoCRUD, in a manner consistent with the overall design of the application. How would you do this? Please go to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> and see for yourself how I did so. I gave logged-in users the ability to create, edit and delete blog entries from the blog/entries page.</p>

<p><a name='a200'></a> </p>

<h2>Implementation</h2>

<p>There are two ways for users to reach the blog/entries page:</p>

<p>1) Select "Blog" from the main menu to go to the blog page. From the blog page, select a member's name from the "Member Blogs" sidebar menu. After doing so, that member's most recent blog entry will appear in the main content area of the blog/entries page.</p>

<p>2) Select "Blog" from the main menu to go to the blog page. From the blog page, click on a title of one of the blog entries in the main content area. After doing so, that blog entry will appear in the main content area of the blog/entries page. </p>

<p>When a logged-in user's blog entry is displayed in the blog/entries page, a sidebar menu will give them the ability to edit or delete it, or to create a new one. Recall that titles of any other blog entries they created will also appear in a sidebar menu. Selecting one of these will make it appear in the main content area. Any blog entry belonging to a logged-in user, displayed in the main content area of the blog/entries page, may be edited or deleted by that user.</p>

<p>After implementing these features I realized I had a problem. The sidebar menu to create a new blog entry only appears for users who already have one or more blog entries. Since new members don't have any, they wouldn't be able to create any blog entries.</p>

<p>The solution? Automatically generate a generic blog entry for each new member when their membership is created. This would have the added benefit of providing an example of how to use HTML tags for style, which could be helpful to users unfamiliar with how to use them.</p>

<p>But what about users who delete their automatically generated blog entry and have no other entries? After doing so, they would be unable to create new blog entries.</p>

<p>The solution? For users with no blog entries, provide a sidebar menu with an item to create a first blog entry, but on which page? On all blog/entries pages, regardless of who's entries appear on the page.</p>

<p><a name='a201'></a></p>

<h2>Reflection</h2>

<p>I question the effectiveness of my design. So far none of the members of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> have created, edited or deleted any of their own blog entries without my help. Whether the interface is too confusing, they don't have enough technical skill, or are not interested enough to do so - I don't know.</p>

<p>Personally, I like the interface, but I would like to know what others think of it.</p>

<p><a name='a101'></a> </p>

<h1>Create New Blog Entry</h1>

<p><a name='a202'></a></p>

<h2>Create Sidebar Menu</h2>

<p>Let's begin by creating a sidebar menu in our blog/entries page. This may be a little confusing, so I will explain this again. There are two situations we are looking for:</p>

<p>1) Logged-in users whose blog entries are being displayed will see a menu giving them the option to create a new blog entry. Later we will add the ability to edit or delete the currently displayed one.</p>

<p>2) Logged-in users who don't have any blog entries will see a menu giving them the option to create their first one.</p>

<p>We begin by putting a flag in the stash for our blog/entries/index.tt template to use. This will be used to create the sidebar menu for logged-in users to create their first blog entry. We add this code to our Blog/Entries.pm controller index action:</p>

<pre><code># Determine if logged-in user has created any blog entries and put
# flag in stash. The template uses this to allow user to create
# first blog entry if they have none.
if ($c-&gt;user_exists) {
  if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id) {
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first;
    if ($row) {
      $c-&gt;stash(user_has_no_blog_entries =&gt; 0);
    }
    else {
      $c-&gt;stash(user_has_no_blog_entries =&gt; 1);
    }
  }
}
</code></pre>

<p>After adding the above code we should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  # Determine if logged-in user has created any blog entries and put
  # flag in stash. The template uses this to allow user to create
  # first blog entry if they have none.
  if ($c-&gt;user_exists) {
    if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id) {
      $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first;
      if ($row) {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 0);
      }
      else {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 1);
      }
    }
  }

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}
</code></pre>

<p>If a user is logged-in and the blog entries displayed on the page are not theirs, we look to see if they have any blog entries and set the flag accordingly.</p>

<p>We create our new sidebar menu in the template by adding this code to blog/entries/index.tt:</p>

<pre><code>[% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% END %]
</code></pre>

<p>We put our new sidebar menu above our existing one displaying blog titles. There is a reason for this. Suppose the user creates 25 blog entries. If our new sidebar menu went below the one displaying 25 blog entry titles, the user would not see it until scrolling down to the bottom of the page.</p>

<p>After adding the above code we should have this:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    [% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    [% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    [% END %]

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
    [% END %]

    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a203'></a> </p>

<h2>Create Controller Actions and Templates</h2>

<p><a name='a300'></a></p>

<h3>unauthorized_action</h3>

<p>We need to protect against users entering URI's into their browser's address bar that would give them access to features we do not want them to have. For example, we don't want non-members to create blog entries. We will enforce this by requiring user's to be logged-in to create blog entries, and detach to a generic "unauthorized_action" page when necessary.</p>

<p>See:</p>

<ul>
<li>Catalyst::Manual::Tutorial::06_Authorization </li>
</ul>

<p>We add the following method to our Root.pm controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Root.pm:</p>

<pre><code>sub unauthorized_action :Chained('/') :PathPart('unauthorized_action') :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(template =&gt; 'unauthorized_action.tt');
}
</code></pre>

<p>Create a template for our new unauthorized_action method:</p>

<p>Yardbird/root/src/unauthorized_action.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Error'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Error&lt;/h3&gt;
    &lt;p&gt;You are not authorized to perform this action.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a301'></a> </p>

<h3>create</h3>

<p>Finally, we add our action method to create new blog entries to our Blog/Entries.pm controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub create :Local :Args(0) {
  my ($self, $c) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  my $row;

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;insert;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}
</code></pre>

<p>We create our corresponding template:</p>

<p>Yardbird/root/src/blog/entries/create.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Create New Blog Entry&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/create') %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Title:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="title" rows="1" cols="88" wrap="virtual"&gt;&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Content:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="content" rows="20" cols="88" wrap="virtual"&gt;&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to create this new blog entry?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application:</p>

<pre><code>Yardbird]$ script/yardbird_server.pl -d -r
</code></pre>

<p>You should now be able to login a member who already has blog entries and create new ones:</p>

<p><img alt="yfc13a1.png" src="http://blogs.perl.org/users/j0e/yfc13a1.png" width="600" height="412" class="mt-image-none" style="" /></p>

<p><img alt="yfc13a2.png" src="http://blogs.perl.org/users/j0e/yfc13a2.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p>You should also be able to login a member who has no blog entries and create their first one:</p>

<p><img alt="yfc13a4.png" src="http://blogs.perl.org/users/j0e/yfc13a4.png" width="600" height="445" class="mt-image-none" style="" /></p>

<p><img alt="yfc13a5.png" src="http://blogs.perl.org/users/j0e/yfc13a5.png" width="600" height="410" class="mt-image-none" style="" /></p>

<p>Don't forget to test your new unauthorized_action routine by entering a URI into your browser's address bar to create a new blog entry without being logged-in:</p>

<p><img alt="yfc13a3.png" src="http://blogs.perl.org/users/j0e/yfc13a3.png" width="600" height="216" class="mt-image-none" style="" /></p>

<p><a name='a102'></a></p>

<h1>Edit Blog Entry</h1>

<p>Notice in our new controller action we not only make sure the user is logged-in, we also ensure the entry they want to edit belongs to them:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub edit :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Prepare to edit blog entry.
  # Detach if user is attempting to edit an entry that doesn't belong to user.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;update;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}
</code></pre>

<p>Yardbird/root/src/blog/entries/edit.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Edit Blog Entry&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Title:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="title" rows="1" cols="88" wrap="virtual"&gt;[% blog_entry.title | html %]&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Content:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="content" rows="20" cols="88" wrap="virtual"&gt;[% blog_entry.content | html %]&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to submit these changes?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Don't forget to add a link to our new edit action in the sidebar menu:</p>

<pre><code>&lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
</code></pre>

<p>After adding the link you should have the following:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% END %]
</code></pre>

<p>Run the application and you should now be able to edit blog entries:</p>

<p><img alt="yfc13b1.png" src="http://blogs.perl.org/users/j0e/yfc13b1.png" width="600" height="411" class="mt-image-none" style="" /></p>

<p><img alt="yfc13b2.png" src="http://blogs.perl.org/users/j0e/yfc13b2.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a103'></a></p>

<h1>Delete Blog Entry</h1>

<p>Deleting entries will look a little different than creating or editing them. We display the entry that will be deleted much like we do when viewing them, with comments (but without the ability to create them) and a prompt to delete at the bottom of the page.</p>

<p>Because we created our blog_comment table with blogid as a foreign key that cascade deletes, all comments belonging to the blog entry are automatically deleted (without our code having to delete them) when the blog entry is deleted.</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub delete :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user attempts to delete an entry that doesn't belong to them.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq 'Yes') {
    $row-&gt;delete; 

    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
  elsif ($submit eq 'No') {
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
}
</code></pre>

<p>Yardbird/root/src/blog/entries/delete.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
      &lt;h3 id="bottom"&gt;Delete Blog Entry&lt;/h3&gt;
      &lt;p&gt;Do you really want to delete this blog entry and corresponding comments?&lt;/p&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
      &lt;h3 id="bottom"&gt;Delete Blog Entry&lt;/h3&gt;
      &lt;p&gt;Do you really want to delete this blog entry?&lt;/p&gt;
    [% END %]

    &lt;form method="post" action="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) %]"&gt;
      &lt;table&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/table&gt;
    &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Don't forget to add a link to our new delete action in the sidebar menu:</p>

<pre><code>&lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt;
</code></pre>

<p>After adding the link you should have the following:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt; 
    &lt;/div&gt;
  &lt;/div&gt;
[% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
[% END %]
</code></pre>

<p>Notice that we create a "bottom" id in the delete.tt template which we target in our sidebar menu link. When pages with long entries to delete load, the delete prompt would not be visible otherwise.</p>

<p>Run the application and you should now be able to delete blog entries:</p>

<p><img alt="yfc13c1.png" src="http://blogs.perl.org/users/j0e/yfc13c1.png" width="600" height="432" class="mt-image-none" style="" /></p>

<p><img alt="yfc13c2.png" src="http://blogs.perl.org/users/j0e/yfc13c2.png" width="600" height="329" class="mt-image-none" style="" /></p>

<p><img alt="yfc13c3.png" src="http://blogs.perl.org/users/j0e/yfc13c3.png" width="600" height="467" class="mt-image-none" style="" /></p>

<p><img alt="yfc13c4.png" src="http://blogs.perl.org/users/j0e/yfc13c4.png" width="600" height="175" class="mt-image-none" style="" /></p>

<p><a name='a104'></a></p>

<h1>Auto-Generate Blog Entry</h1>

<p>This is what we now have to create a new member:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated, return to home
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p>We auto-generate a new member's first blog entry after validating the form and inserting their user info into the database, before returning home:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c-&gt;model('DB::User')-&gt;most_recent-&gt;first; 
  my $userid = $row-&gt;id; 
  my $name = $row-&gt;name; 

  # Create new member's first blog entry.
  $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
  $row-&gt;userid($userid);
  $row-&gt;title("Welcome ".$name."!");
  $row-&gt;content(
"&lt;h3&gt;You Are Now A Member Of The YARDBIRD Fan Club!&lt;/h3&gt;
&lt;p&gt;You may now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post blog entries and comments via the Blog pages.&lt;/li&gt;
&lt;li&gt;Optionally provide your contact and other information to other users via the Members pages.&lt;/li&gt;
&lt;li&gt;View other member's contact information via the Members pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About This Blog Entry&lt;/h3&gt;
&lt;p&gt;This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.&lt;/p&gt;
&lt;p&gt;It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;"
);
  $row-&gt;created(DateTime-&gt;now);
  $row-&gt;insert; 

  # Return to homepage.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p>Create a new ResultSet class to search for the most recently added new member:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm:</p>

<pre><code>package Yardbird::Schema::ResultSet::User;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, { order_by =&gt; {-desc =&gt; ['id']} });
} 

1;
</code></pre>

<p>Run the application and you should be able to create a new member with their first blog entry automatically generated for them:</p>

<p><img alt="yfc13d1.png" src="http://blogs.perl.org/users/j0e/yfc13d1.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a105'></a></p>

<h1>Summary</h1>

<p><a name='a204'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</h2>

<pre><code>package Yardbird::Controller::Blog::Entries;
use Moose;
use namespace::autoclean;
use Yardbird::Form::BlogComment;

BEGIN { extends 'Catalyst::Controller'; }

has 'comment_form' =&gt; (
  isa =&gt; 'Yardbird::Form::BlogComment',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::BlogComment-&gt;new }
); 

sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  # Determine if logged-in user has created any blog entries and put
  # flag in stash. The template uses this to allow user to create
  # first blog entry if they have none.
  if ($c-&gt;user_exists) {
    if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id) {
      $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first;
      if ($row) {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 0);
      }
      else {
        $c-&gt;stash(user_has_no_blog_entries =&gt; 1);
      }
    }
  }

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}

sub create :Local :Args(0) {
  my ($self, $c) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  my $row;

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;insert;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}

sub edit :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Prepare to edit blog entry.
  # Detach if user is attempting to edit an entry that doesn't belong to user.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq "Yes") {
    $row-&gt;title($c-&gt;req-&gt;params-&gt;{title});
    $row-&gt;content($c-&gt;req-&gt;params-&gt;{content});
    return if !$row-&gt;title || !$row-&gt;content;
    $row-&gt;update;
  }

  if ($submit eq "Yes" || $submit eq "No") {
    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
}

sub delete :Local :Args(1) {
  my ($self, $c, $bID) = @_;

  # Detach if user is not logged-in.
  $c-&gt;detach('/unauthorized_action') if !$c-&gt;user_exists;

  # Detach if user attempts to delete an entry that doesn't belong to them.
  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first; 
  $c-&gt;detach('/unauthorized_action') if ($c-&gt;user-&gt;id != $row-&gt;userid-&gt;id);

  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  my $submit = $c-&gt;req-&gt;params-&gt;{submit};
  if ($submit eq 'Yes') {
    $row-&gt;delete; 

    # Get the most recent blog entry if one exists and display it, else go to the /blog/index page.
    $row = $c-&gt;model('DB::Blog')-&gt;all_user($c-&gt;user-&gt;id)-&gt;first; 
    if ($row) {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $row-&gt;id));
    }
    else {
      $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/index'));
    }
  }
  elsif ($submit eq 'No') {
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a205'></a></p>

<h2>Yardbird/root/src/blog/entries/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    [% IF (c.user_exists &amp;&amp; (c.user.id == blog_entry.userid.id)) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create New Blog Entry&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;Edit Blog Entry&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) _ '#bottom' %]"&gt;Delete Blog Entry&lt;/a&gt;&lt;/p&gt; 
        &lt;/div&gt;
      &lt;/div&gt;
    [% ELSIF (c.user_exists &amp;&amp; user_has_no_blog_entries) %]
      &lt;div class="sidebar_item"&gt;
        &lt;div class="sidebar_item_content"&gt;
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/create') %]"&gt;Create First Blog Entry&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    [% END %]

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
    [% END %]

    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a206'></a></p>

<h2>Yardbird/root/src/blog/entries/create.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% c.user.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Create New Blog Entry&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/create') %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Title:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="title" rows="1" cols="88" wrap="virtual"&gt;&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Content:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="content" rows="20" cols="88" wrap="virtual"&gt;&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to create this new blog entry?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a207'></a></p>

<h2>Yardbird/root/src/blog/entries/edit.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
  &lt;h3&gt;Edit Blog Entry&lt;/h3&gt;

  &lt;form method="post" action="[% c.uri_for_action('/blog/entries/edit', blog_entry.id) %]"&gt;
    &lt;table&gt;

      &lt;tr&gt;
        &lt;td&gt;Title:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="title" rows="1" cols="88" wrap="virtual"&gt;[% blog_entry.title | html %]&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Content:&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;textarea name="content" rows="20" cols="88" wrap="virtual"&gt;[% blog_entry.content | html %]&lt;/textarea&gt;&lt;/td&gt;
      &lt;/tr&gt;

      &lt;tr&gt;
        &lt;td&gt;&lt;p&gt;Do you really want to submit these changes?&lt;/p&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
      &lt;/tr&gt;

    &lt;/table&gt;
  &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a208'></a></p>

<h2>Yardbird/root/src/blog/entries/delete.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
      &lt;h3 id="bottom"&gt;Delete Blog Entry&lt;/h3&gt;
      &lt;p&gt;Do you really want to delete this blog entry and corresponding comments?&lt;/p&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
      &lt;h3 id="bottom"&gt;Delete Blog Entry&lt;/h3&gt;
      &lt;p&gt;Do you really want to delete this blog entry?&lt;/p&gt;
    [% END %]

    &lt;form method="post" action="[% c.uri_for_action('/blog/entries/delete', blog_entry.id) %]"&gt;
      &lt;table&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;input type="submit" name="submit" value="Yes" /&gt; &lt;input type="submit" name="submit" value="No" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/table&gt;
    &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a209'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Root.pm</h2>

<pre><code>package Yardbird::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }

#
# Sets the actions in this controller to be registered with no prefix
# so they function identically to actions created in MyApp.pm
#
__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub unauthorized_action :Chained('/') :PathPart('unauthorized_action') :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(template =&gt; 'unauthorized_action.tt');
}

sub default :Path {
    my ( $self, $c ) = @_;
    $c-&gt;response-&gt;body( 'Page not found' );
    $c-&gt;response-&gt;status(404);
}

sub end : ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a210'></a></p>

<h2>Yardbird/root/src/unauthorized_action.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Error'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Error&lt;/h3&gt;
    &lt;p&gt;You are not authorized to perform this action.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a211'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Member.pm</h2>

<pre><code>package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;
use Yardbird::Form::User; 

BEGIN { extends 'Catalyst::Controller'; }

has 'form' =&gt; (
  isa =&gt; 'Yardbird::Form::User',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::User-&gt;new }
);

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched Yardbird::Controller::Member in Member.');
}

sub about :Local :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database.
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c-&gt;model('DB::User')-&gt;most_recent-&gt;first; 
  my $userid = $row-&gt;id; 
  my $name = $row-&gt;name; 

  # Create new member's first blog entry.
  $row = $c-&gt;model('DB::Blog')-&gt;new_result({}); 
  $row-&gt;userid($userid);
  $row-&gt;title("Welcome ".$name."!");
  $row-&gt;content(
"&lt;h3&gt;You Are Now A Member Of The YARDBIRD Fan Club!&lt;/h3&gt;
&lt;p&gt;You may now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post blog entries and comments via the Blog pages.&lt;/li&gt;
&lt;li&gt;Optionally provide your contact and other information to other users via the Members pages.&lt;/li&gt;
&lt;li&gt;View other member's contact information via the Members pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About This Blog Entry&lt;/h3&gt;
&lt;p&gt;This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.&lt;/p&gt;
&lt;p&gt;It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;"
);
  $row-&gt;created(DateTime-&gt;now);
  $row-&gt;insert; 

  # Return to homepage.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a212'></a></p>

<h2>Yardbird/lib/Yardbird/Schema/ResultSet/User.pm</h2>

<pre><code>package Yardbird::Schema::ResultSet::User;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, { order_by =&gt; {-desc =&gt; ['id']} });
} 

1;
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 12: Create and View Comments</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4464</id>

    <published>2013-03-24T07:02:20Z</published>
    <updated>2013-03-24T07:48:14Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a100'>Create Comments</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a200'>Create HTML::FormHandler Form</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a201'>Edit Controller</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a202'>Edit Template</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a101'>View Comments</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a203'>Create ResultSet Class</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a204'>Edit Blog.pm Controller</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a205'>Edit blog/index.tt Template</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a206'>Edit Stylesheet</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a207'>Edit Blog/Entries.pm Controller</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a208'>Edit blog/entries/index.tt Template</a></li>
</ul> 

<p><li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a102'>Summary</a></li></p>

<ul> 
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a209'>Yardbird/lib/Yardbird/Controller/Blog.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a210'>Yardbird/root/src/blog/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a211'>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a212'>Yardbird/root/src/blog/entries/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a213'>Yardbird/root/static/css/main.css</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a214'>Yardbird/lib/Yardbird/Form/BlogComment.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html#a215'>Yardbird/lib/Yardbird/Schema/ResultSet/BlogComment.pm</a></li>
</ul> 

<p></ul></p>
]]>
        <![CDATA[<p><a name='a100'></a> </p>

<h1>Create Comments</h1>

<p>Recall that we already have a table in our database that will allow us to create and view comments:</p>

<p>yardbird.sql:</p>

<pre><code>CREATE TABLE blog_comment (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  blogid  INT UNSIGNED NOT NULL,
  -- userid of the commenter (NOT THE userid OF THE BLOG ENTRY)
  userid  INT UNSIGNED NOT NULL,
  content TEXT NOT NULL,
  created TIMESTAMP NOT NULL,
  FOREIGN KEY (blogid) REFERENCES blog(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id) 
) ENGINE=InnoDB;
</code></pre>

<p>We will create comments with an HTML::FormHandler form, much like we created new members. You may want to review <a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#400' target='_blank'>Notes from a Newbie 10: Authentication/Authorization</a> and these files you already created:</p>

<ul>
<li>Yardbird/lib/Yardbird/Controller/Member.pm</li>
<li>Yardbird/root/src/member/create.tt</li>
<li>Yardbird/lib/Yardbird/Form/User.pm</li>
</ul>

<p><a name='a200'></a> </p>

<h2>Create HTML::FormHandler Form</h2>

<p>Let's start by creating the form. Recall that Catalyst provides no helper script to create the form, you create it yourself:</p>

<p>Yardbird/lib/Yardbird/Form/BlogComment.pm:</p>

<pre><code>package Yardbird::Form::BlogComment;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';

has '+item_class' =&gt; ( default =&gt; 'BlogComment' );

has_field 'content' =&gt; (
  type =&gt; 'TextArea',
  cols =&gt; 88,
  rows =&gt; 10,
  required =&gt; 1,
  do_label =&gt; 0,
  tags =&gt; { wrapper_tag =&gt; 'p' },
);

has_field 'submit' =&gt; (
  type =&gt; 'Submit',
  value =&gt; 'Submit',
);

no HTML::FormHandler::Moose;
1;
</code></pre>

<p><a name='a201'></a> </p>

<h2>Edit Controller</h2>

<p>We need to setup the Blog/Entries.pm controller to use the form by adding this to it:</p>

<pre><code>use Yardbird::Form::BlogComment;
</code></pre>

<p>We will also need to add this to it:</p>

<pre><code>has 'comment_form' =&gt; (
  isa =&gt; 'Yardbird::Form::BlogComment',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::BlogComment-&gt;new }
);
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>package Yardbird::Controller::Blog::Entries;
use Moose;
use namespace::autoclean;
use Yardbird::Form::BlogComment;

BEGIN {extends 'Catalyst::Controller'; }

has 'comment_form' =&gt; (
  isa =&gt; 'Yardbird::Form::BlogComment',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::BlogComment-&gt;new }
);
</code></pre>

<p>Then we can use the form in our index action with this code:</p>

<pre><code># Don't allow comments to be added if user is not logged in.
if ($c-&gt;user_exists) {
  $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

  $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
  $row-&gt;blogid($bID);
  $row-&gt;userid($c-&gt;user-&gt;id);
  $row-&gt;created(DateTime-&gt;now);

  # Validate and add database row
  return unless $self-&gt;comment_form-&gt;process(
    item =&gt; $row,
    params =&gt; $c-&gt;req-&gt;params,
  );

  # Form validated, refresh the page.
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
}
else {
  return;
}
</code></pre>

<p>After making the above change you should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}
</code></pre>

<p>Compare your controller action for creating new members to the one you just edited to create comments:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated, return to home
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p>Unlike when creating new members, we only allow logged-in users to add comments.</p>

<p>Notice also that we create a new row object and pass it to the process method, rather than passing a row id as we did when creating new members. For this reason it is unnecessary for our Blog/Entries.pm index method to pass the schema to the process method.</p>

<p><a name='a202'></a> </p>

<h2>Edit Template</h2>

<p>Since our form is so simple, we can display it to appear as we wish with much less work than was needed to display our form to create new members.</p>

<p>This is what we now do to display content:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>&lt;div id="content_with_sidebar"&gt;
  &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
  &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
&lt;/div&gt;
</code></pre>

<p>Add the following below where we display blog_entry.content:</p>

<pre><code>&lt;h3&gt;Leave a comment&lt;/h3&gt;
[% IF c.user_exists %] 
  &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
  [% form.render %] 
[% ELSE %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
[% END %]
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application and view one of the blog entries (by clicking on it's title in the content area) and you should now be able to create a comment:</p>

<pre><code>Yardbird]$ script/yardbird_server.pl -d -r
</code></pre>

<p>When not logged-in you should have options to login to comment or become a member:</p>

<p><img alt="yfc12a1.png" src="http://blogs.perl.org/users/j0e/yfc12a1.png" width="600" height="263" class="mt-image-none" style="" /></p>

<p>When logged-in you should see your new form and be able to submit a comment:</p>

<p><img alt="yfc12a2.png" src="http://blogs.perl.org/users/j0e/yfc12a2.png" width="600" height="394" class="mt-image-none" style="" /></p>

<p>We haven't added the feature to view comments yet, but you can use AutoCRUD to do so by giving your browser the correct URI:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>

<p>Select the "Blog Comment" table and you should see the new comment you created:</p>

<p><img alt="yfc12a3.png" src="http://blogs.perl.org/users/j0e/yfc12a3.png" width="600" height="176" class="mt-image-none" style="" /></p>

<p><a name='a101'></a> </p>

<h1>View Comments</h1>

<p><a name='a203'></a> </p>

<h2>Create ResultSet Class</h2>

<p>We are going to need some ResultSet methods to search for comments, so I'll give them all to you at once. You may want to go back to <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#100' target='_blank'>Notes from a Newbie Experiment 02: DBIx::Class</a> and review how we use ResultSets. You will need to create the following file:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/BlogComment.pm:</p>

<pre><code>package Yardbird::Schema::ResultSet::BlogComment;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific_comment {
  my ($self, $id) = @_;

  return $self-&gt;search({id =&gt; $id});
} 

sub specific_blog {
  my ($self, $bID) = @_;

  return $self-&gt;search({blogid =&gt; $bID});
} 

sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
} 

1;
</code></pre>

<p><a name='a204'></a> </p>

<h2>Edit Blog.pm Controller</h2>

<p>We need to put a ResultSet in the stash for our template:</p>

<pre><code>$c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment')); 

  my @members;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    my $blog_entry = $c-&gt;model('DB::Blog')-&gt;all_user($user-&gt;id)-&gt;first;
    push @members, { name =&gt; $user-&gt;name, bid =&gt; $blog_entry-&gt;id } if $blog_entry; 
  }
  my @sorted_members = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @members; 
  $c-&gt;stash(sorted_members =&gt; [@sorted_members]);

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p><a name='a205'></a> </p>

<h2>Edit blog/index.tt Template</h2>

<p>This is what we now do to display content:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>&lt;div id="content_with_sidebar"&gt;
  [% FOREACH blog_entry IN blog_entries %]
    &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
  [% END %]
&lt;/div&gt;
</code></pre>

<p>Add the following below where we display blog_entry.content: </p>

<pre><code>[% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
[% IF blog_comments_rs %]
  &lt;div class="top_of_comments"&gt;&lt;/div&gt;
  [% between_comments_flg = 0 %]
  [% FOREACH blog_comment IN blog_comments_rs %]
    [% IF between_comments_flg %]
      &lt;div class="between_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      [% between_comments_flg = 1 %]
    [% END %]
    &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
  [% END %]
  &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
[% ELSE %]
  &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
[% END %]
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH blog_entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
          &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Blogs&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH member IN sorted_members %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', member.bid) %]"&gt;[% member.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

      [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
      [% IF blog_comments_rs %]
        &lt;div class="top_of_comments"&gt;&lt;/div&gt;
        [% between_comments_flg = 0 %]
        [% FOREACH blog_comment IN blog_comments_rs %]
          [% IF between_comments_flg %]
            &lt;div class="between_comments"&gt;&lt;/div&gt;
          [% ELSE %]
            [% between_comments_flg = 1 %]
          [% END %]
          &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
          &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
        [% END %]
        &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
      [% ELSE %]
        &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
      [% END %]

    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a206'></a> </p>

<h2>Edit Stylesheet</h2>

<p>Our template needs a few new styles added to our stylesheet:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.bottom_of_content {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.5em;
  margin-bottom: 1.3em;
}

.top_of_comments {
  margin-top: 1.3em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.between_comments {
  margin-top: -0.6em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.bottom_of_comments {
  border-bottom: 2px solid #87ceeb;
  margin-top: -0.6em;
  margin-bottom: 1.3em;
}

.comment_content {
  margin-bottom: 1em;
}

.comment_subtitle {
  font-size: 0.7em;
  color: #004080; 
  margin-top: 1.2em;
}
</code></pre>

<p>Run the application and go to the Blog page and you should see decent looking comments, with horizontal blue lines separating blog entries and comments:</p>

<p><img alt="yfc12b1.png" src="http://blogs.perl.org/users/j0e/yfc12b1.png" width="600" height="451" class="mt-image-none" style="" /></p>

<p><a name='a207'></a> </p>

<h2>Edit Blog/Entries.pm Controller</h2>

<p>Again like we did above, we need to put a ResultSet in the stash for our template:</p>

<pre><code>$c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}
</code></pre>

<p><a name='a208'></a> </p>

<h2>Edit blog/entries/index.tt Template</h2>

<p>This is what we now do to display content: </p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>&lt;div id="content_with_sidebar"&gt;
  &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
  &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
  &lt;h3&gt;Leave a comment&lt;/h3&gt;
  [% IF c.user_exists %] 
    &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
    [% form.render %] 
  [% ELSE %]
    &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
  [% END %]
&lt;/div&gt;
</code></pre>

<p>Again, similar to what we did above, we add the following between where we display blog_entry.content and "Leave a comment":</p>

<pre><code>[% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
[% IF blog_comments_rs %]
  &lt;div class="top_of_comments"&gt;&lt;/div&gt;
  [% between_comments_flg = 0 %]
  [% FOREACH blog_comment IN blog_comments_rs %]
    [% IF between_comments_flg %]
      &lt;div class="between_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      [% between_comments_flg = 1 %]
    [% END %]
    &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
  [% END %]
  &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
[% ELSE %]
  &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
[% END %]
</code></pre>

<p>After adding the above code you should have this:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
    [% END %]

    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run the application and you should now be able to see comments in the blog/entries pages:</p>

<p><img alt="yfc12b2.png" src="http://blogs.perl.org/users/j0e/yfc12b2.png" width="600" height="315" class="mt-image-none" style="" /></p>

<p><a name='a102'></a> </p>

<h1>Summary</h1>

<p><a name='a209'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Blog.pm</h2>

<pre><code>package Yardbird::Controller::Blog;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment')); 

  my @members;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    my $blog_entry = $c-&gt;model('DB::Blog')-&gt;all_user($user-&gt;id)-&gt;first;
    push @members, { name =&gt; $user-&gt;name, bid =&gt; $blog_entry-&gt;id } if $blog_entry; 
  }
  my @sorted_members = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @members; 
  $c-&gt;stash(sorted_members =&gt; [@sorted_members]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a210'></a> </p>

<h2>Yardbird/root/src/blog/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH blog_entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
          &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Blogs&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH member IN sorted_members %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', member.bid) %]"&gt;[% member.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

      [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
      [% IF blog_comments_rs %]
        &lt;div class="top_of_comments"&gt;&lt;/div&gt;
        [% between_comments_flg = 0 %]
        [% FOREACH blog_comment IN blog_comments_rs %]
          [% IF between_comments_flg %]
            &lt;div class="between_comments"&gt;&lt;/div&gt;
          [% ELSE %]
            [% between_comments_flg = 1 %]
          [% END %]
          &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
          &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
        [% END %]
        &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
      [% ELSE %]
        &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
      [% END %]

    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a211'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</h2>

<pre><code>package Yardbird::Controller::Blog::Entries;
use Moose;
use namespace::autoclean;
use Yardbird::Form::BlogComment;

BEGIN { extends 'Catalyst::Controller'; }

has 'comment_form' =&gt; (
  isa =&gt; 'Yardbird::Form::BlogComment',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::BlogComment-&gt;new }
); 

sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;stash(blog_comments =&gt; $c-&gt;model('DB::BlogComment'));

  # Don't allow comments to be added if user is not logged in.
  if ($c-&gt;user_exists) {
    $c-&gt;stash(template =&gt; 'blog/entries/index.tt', form =&gt; $self-&gt;comment_form);

    $row = $c-&gt;model('DB::BlogComment')-&gt;new_result({});
    $row-&gt;blogid($bID);
    $row-&gt;userid($c-&gt;user-&gt;id);
    $row-&gt;created(DateTime-&gt;now);

    # Validate and add database row
    return unless $self-&gt;comment_form-&gt;process(
      item =&gt; $row,
      params =&gt; $c-&gt;req-&gt;params,
    );

    # Form validated, refresh the page.
    $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/blog/entries/index', $bID));
  }
  else {
    return;
  }
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a212'></a> </p>

<h2>Yardbird/root/src/blog/entries/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;

    [% blog_comments_rs = blog_comments.specific_blog(blog_entry.id) %]
    [% IF blog_comments_rs %]
      &lt;div class="top_of_comments"&gt;&lt;/div&gt;
      [% between_comments_flg = 0 %]
      [% FOREACH blog_comment IN blog_comments_rs %]
        [% IF between_comments_flg %]
          &lt;div class="between_comments"&gt;&lt;/div&gt;
        [% ELSE %]
          [% between_comments_flg = 1 %]
        [% END %]
        &lt;p class="comment_subtitle"&gt;Comment by &lt;strong&gt;[% blog_comment.userid.name %]&lt;/strong&gt; on [% blog_comment.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        &lt;p class="comment_content"&gt;[% blog_comment.content %]&lt;/p&gt;
      [% END %]
      &lt;div class="bottom_of_comments"&gt;&lt;/div&gt;
    [% ELSE %]
      &lt;div class="bottom_of_content"&gt;&lt;/div&gt;
    [% END %]

    &lt;h3&gt;Leave a comment&lt;/h3&gt;
    [% IF c.user_exists %] 
      &lt;p&gt;(You may use HTML tags for style)&lt;/p&gt; 
      [% form.render %] 
    [% ELSE %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login to comment.&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/p&gt; 
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a213'></a> </p>

<h2>Yardbird/root/static/css/main.css</h2>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}

#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}

.content_title {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0.3em;
}

.content_subtitle {
  font-size: 0.8em;
  margin-top: -1.6em;
}

.content_title a {
  color: #004080;
  text-decoration: none; 
}

.content_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_title {
  font-size: 0.9em;
  font-weight: bold;
}

.sidebar_item_title a {
  color: #004080;
  text-decoration: none; 
}

.sidebar_item_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_subtitle {
  font-size: 0.65em;
  margin-top: -1.5em;
}

.sidebar_item_sub_subtitle {
  font-size: 0.65em;
  margin-top: -1.0em;
}

.bottom_of_content {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.5em;
  margin-bottom: 1.3em;
}

.top_of_comments {
  margin-top: 1.3em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.between_comments {
  margin-top: -0.6em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.bottom_of_comments {
  border-bottom: 2px solid #87ceeb;
  margin-top: -0.6em;
  margin-bottom: 1.3em;
}

.comment_content {
  margin-bottom: 1em;
}

.comment_subtitle {
  font-size: 0.7em;
  color: #004080; 
  margin-top: 1.2em;
}
</code></pre>

<p><a name='a214'></a> </p>

<h2>Yardbird/lib/Yardbird/Form/BlogComment.pm</h2>

<pre><code>package Yardbird::Form::BlogComment;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';

has '+item_class' =&gt; ( default =&gt; 'BlogComment' );

has_field 'content' =&gt; (
  type =&gt; 'TextArea',
  cols =&gt; 88,
  rows =&gt; 10,
  required =&gt; 1,
  do_label =&gt; 0,
  tags =&gt; { wrapper_tag =&gt; 'p' },
);

has_field 'submit' =&gt; (
  type =&gt; 'Submit',
  value =&gt; 'Submit',
);

no HTML::FormHandler::Moose;
1;
</code></pre>

<p><a name='a215'></a> </p>

<h2>Yardbird/lib/Yardbird/Schema/ResultSet/BlogComment.pm</h2>

<pre><code>package Yardbird::Schema::ResultSet::BlogComment;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific_comment {
  my ($self, $id) = @_;

  return $self-&gt;search({id =&gt; $id});
} 

sub specific_blog {
  my ($self, $bID) = @_;

  return $self-&gt;search({blogid =&gt; $bID});
} 

sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
} 

1;
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 11: View Blogs</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4409</id>

    <published>2013-03-11T02:01:10Z</published>
    <updated>2013-03-24T05:14:07Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<p>We experimented then started over and now have a decent looking application with authentication/authorization features:</p>

<ul>
<li>Create new member</li>
<li>Login member</li>
<li>Logout member</li>
</ul>

<p>We also have other things going, such as a complete database with all tables and columns needed to record membership and create and use blog entries and comments. We know the difference between DBIC Row, Result and ResultSets, and how to use them to store and retrieve data. We've used both an HTML::FormHandler form and one we rolled ourselves. We have header and footer templates to minimize code duplication while providing an attractive, consistent look to our pages. We have a good start on a css stylesheet, with a few basic styles we've been using in our templates.</p>

<p>In our experiments we figured out how to display and link to blog titles and content, with little concern for their appearance on a page. We now readdress the issue of displaying them, with a few new styles, search and sort routines for improvements.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a100'>AutoCRUD</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a101'>View Recent Blog Entries</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a102'>View a Specific Blog Entry</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a200'>Click on Title in Main Content Area</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a201'>Click on Title in Sidebar Menu</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a300'>blog/entries Page</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a301'>blog Page</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a202'>Click on Member Name in Sidebar Menu</a></li>
</ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a103'>Summary</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a203'>Yardbird/lib/Yardbird.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a204'>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a205'>Yardbird/lib/Yardbird/Controller/Blog.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a206'>Yardbird/root/src/blog/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a207'>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a208'>Yardbird/root/src/blog/entries/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a209'>Yardbird/root/src/header.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html#a210'>Yardbird/root/static/css/main.css</a></li>
</ul>

<p></ul></p>
]]>
        <![CDATA[<p><a name='a100'></a> </p>

<h1>AutoCRUD</h1>

<p>We won't add the feature to create blogs until after adding features to view them, therefore we will use AutoCRUD in the meantime. We will want a few entries to experiment with as we add features to view them.</p>

<p>All it takes to use AutoCRUD is two things:</p>

<p>1) Use AutoCRUD in your application class:</p>

<p>Yardbird/lib/Yardbird.pm:</p>

<pre><code>use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple

  AutoCRUD

  Authentication
  Authorization::Roles

  Session
  Session::Store::File
  Session::State::Cookie 
/;
</code></pre>

<p>2) Give your browser the location to use AutoCRUD:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>

<p>Run your application with autocrud and create a few blog entries to test with. They will be useful as we add features to view them.</p>

<p><a name='a101'></a></p>

<h1>View Recent Blog Entries</h1>

<p>We are ready to add Blog features, where shall we start?</p>

<p>When a user, whether logged-in or not, selects 'Blog' from the main menu, let's have the most recent blog entries displayed, ordered with the most recent at the top.</p>

<p>We did this already in <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#206' target='_blank'>Experiment 02: Create ResultSet Class (Query) Search Methods</a>, but this time we will put effort into formatting page output to make it look decent. We will also be concerned with URI paths as we add our application features.</p>

<p>We already created all the ResultSet search methods we need in <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#206' target='_blank'>Experiment 02: Create ResultSet Class (Query) Search Methods</a>. Copy the Blog ResultSet file over to our new application from Experiment 02 or use this one, they are the same file:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm:</p>

<pre><code>package Yardbird::Schema::ResultSet::Blog;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific {
  my ($self, $bID) = @_;

  return $self-&gt;search({id =&gt; $bID});
} 

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, {order_by =&gt; {-desc =&gt; ['id']}, rows =&gt; 4});
} 

sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
} 

1;
</code></pre>

<p>We will create a new controller, and index method to search for and retrieve a list of Row objects to put in the stash for a template to use. These Row objects will contain our blog entries.</p>

<p>Create a controller:</p>

<pre><code>Yardbird]$ script/yardbird_create.pl controller Blog
</code></pre>

<p>Edit the new Blog.pm controller's index method to search for the most recent blog entries and return the results in the stash. Since the method call is in list context, a list of row objects is returned and put in the stash:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]); 
  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>We won't create any Sidebar Menus yet but will soon do so, therefore we setup our page to display content with sidebar:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Add these styles to the stylesheet:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.content_title {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0.3em;
}

.content_subtitle {
  font-size: 0.8em;
  margin-top: -1.6em;
}
</code></pre>

<p>Enable a link to blog/index:</p>

<p>Change this:</p>

<p>Yardbird/root/src/header.tt:</p>

<pre><code>&lt;li&gt;Blog&lt;/li&gt;
</code></pre>

<p>to this:</p>

<pre><code>&lt;li&gt;&lt;a href="[% c.uri_for_action('blog/index') %]"&gt;Blog&lt;/a&gt;&lt;/li&gt;
</code></pre>

<p>Run it and you should get a decent page with most recent blog entries at the top:</p>

<p><img alt="yfc11b1.png" src="http://blogs.perl.org/users/j0e/yfc11b1.png" width="600" height="340" class="mt-image-none" style="" /></p>

<p><a name='a102'></a> </p>

<h1>View a Specific Blog Entry</h1>

<p>Now that we have the basic blog page format down, we can begin concentrating on user-interface details such as:</p>

<ul>
<li>How to view any specific blog entry?</li>
<li>How to create, edit and delete blog entries?</li>
</ul>

<p>How would you implement these features? Enable a search by title or author? Select from a long list of blog entries? Ouch, what if we have hundreds or thousands of blog entries?</p>

<p>I decided to try and have an interface similar to <a href='http://blogs.perl.org' target='_blank'>blogs.perl.org</a>. Rather than confusing you with a poor explanation, please go to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> and see for yourself how to become a member, and view, create, edit and delete blog entries and comments. Use the site and learn how the interface works, then come back and we'll continue to build this application.</p>

<p><a name='a200'></a> </p>

<h2>Click on Title in Main Content Area</h2>

<p>We want to be able to click on the title of any blog entry in the main content area of our blog page, then go to the blog/entries page for that entry.</p>

<p>Let's begin by creating a new blog/entries controller, and index method to display the entry of a selected title:</p>

<pre><code>Yardbird]$ script/yardbird_create.pl controller Blog::Entries
</code></pre>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Our new blog/entries/index template will display a specific blog entry in the main content area:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Make main content blog entry titles into links:</p>

<p>Change this:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>&lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
</code></pre>

<p>to this:</p>

<pre><code>&lt;p class="content_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
</code></pre>

<p>We want the titles to appear as links so we add a few new styles to our stylesheet:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.content_title a {
  color: #004080;
  text-decoration: none; 
}

.content_title a:hover {
  color: #004080;
  text-decoration: underline; 
}
</code></pre>

<p>Run it and you should get a decent blog/entries page when clicking a blog page main content blog entry title:</p>

<p><img alt="yfc11c1.png" src="http://blogs.perl.org/users/j0e/yfc11c1.png" width="600" height="230" class="mt-image-none" style="" /></p>

<p><a name='a201'></a></p>

<h2>Click on Title in Sidebar Menu</h2>

<p><a name='a300'></a></p>

<h3>blog/entries Page</h3>

<p>Let's add a sidebar menu to the blog/entries page, containing titles of all blog entries authored by the member who's blog entry is being displayed. Selecting a title will cause it's corresponding blog entry to be displayed in the main content area of the same page.</p>

<p>Our controller will need to put a list of row objects in the stash for our template, corresponding to all of a member's blog entries.</p>

<p>Add this line to your index method:</p>

<pre><code>$c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
</code></pre>

<p>After adding it you should have this:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm:</p>

<pre><code>sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Add a new sidebar menu to the template:</p>

<p>Yardbird/root/src/blog/entries/index.tt:</p>

<pre><code>&lt;div id="sidebar_wrapper"&gt;
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      [% FOREACH entry IN blog_entries %]
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      [% END %]
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>After doing so you should have this:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Our new sidebar menu needs styles which we add to our stylesheet:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.sidebar_item_title {
  font-size: 0.9em;
  font-weight: bold;
}

.sidebar_item_title a {
  color: #004080;
  text-decoration: none; 
}

.sidebar_item_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_subtitle {
  font-size: 0.65em;
  margin-top: -1.5em;
}
</code></pre>

<p>Run it and you should get an attractive, functional sidebar menu on the blog/entries page:</p>

<p><img alt="yfc11d1.png" src="http://blogs.perl.org/users/j0e/yfc11d1.png" width="600" height="231" class="mt-image-none" style="" /></p>

<p><a name='a301'></a></p>

<h3>blog Page</h3>

<p>We also need to add a sidebar menu to the blog page, containing titles that link to the most recent blog entries on that page.</p>

<p>Our Blog controller index method is already putting the most recent blog entries into the stack, and requires no other changes:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);
  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>It's corresponding template is where we add the new feature, this is what we have now:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>We will first add ID selectors to identify the blog entries to link to, then create the new sidebar menu with links to the blog entries identified by these ID selectors.</p>

<p>First add ID selectors to identify blog entries to link to. I concatenate 'bid' with blog_entry.id so that the resulting ID selector does not begin with a number. Some browsers do not recognize ID selectors that begin with a number.</p>

<pre><code>id="[% 'bid' _ blog_entry.id %]"
</code></pre>

<p>After adding the ID selectors we have:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Then we add the new sidebar menu:</p>

<pre><code>&lt;div id="sidebar_wrapper"&gt;
  &lt;div class="sidebar_item"&gt;
    &lt;div class="sidebar_item_content"&gt;
      &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
      [% FOREACH blog_entry IN blog_entries %]
        &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
        &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
        &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      [% END %]
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>After adding the new sidebar menu we have:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH blog_entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
          &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Notice the use of our new <code>sidebar_item_sub_subtitle</code> class above. We need to add the corresponding style:</p>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>.sidebar_item_sub_subtitle {
  font-size: 0.65em;
  margin-top: -1.0em;
}
</code></pre>

<p>Run it and you should get a decent looking sidebar menu on the blog page, with links to the recent blog entries on that page:</p>

<p><img alt="yfc11e1.png" src="http://blogs.perl.org/users/j0e/yfc11e1.png" width="600" height="342" class="mt-image-none" style="" /></p>

<p>If (like me) you don't have enough content in your blog entries to fill up a page, you will need to add some to test it.</p>

<p>After adding "A Long Blog Entry" I clicked on it's title in the Recent Blog Entries sidebar menu and got this:</p>

<p><img alt="yfc11e2.png" src="http://blogs.perl.org/users/j0e/yfc11e2.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a202'></a></p>

<h2>Click on Member Name in Sidebar Menu</h2>

<p>Recall that we use a rather complicated means to determine a user's name:</p>

<p>Yardbird/lib/Yardbird/Schema/Result/User.pm:</p>

<pre><code>sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}
</code></pre>

<p>This makes it a little more difficult to sort by member names:</p>

<pre><code>my @members;
for my $user ($c-&gt;model('DB::User')-&gt;all) {
  my $blog_entry = $c-&gt;model('DB::Blog')-&gt;all_user($user-&gt;id)-&gt;first;
  push @members, { name =&gt; $user-&gt;name, bid =&gt; $blog_entry-&gt;id } if $blog_entry; 
}
my @sorted_members = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @members; 
$c-&gt;stash(sorted_members =&gt; [@sorted_members]);
</code></pre>

<p>We loop through the users. If a user has any blog entries, we add their name and blog id of their most recent blog entry to @members. When done looping through all users we sort @members and put the result in the stash for our template.</p>

<p>Add the code to our controller index method and we have:</p>

<p>Yardbird/lib/Yardbird/Controller/Blog.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);

  my @members;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    my $blog_entry = $c-&gt;model('DB::Blog')-&gt;all_user($user-&gt;id)-&gt;first;
    push @members, { name =&gt; $user-&gt;name, bid =&gt; $blog_entry-&gt;id } if $blog_entry; 
  }
  my @sorted_members = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @members; 
  $c-&gt;stash(sorted_members =&gt; [@sorted_members]);

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Add the new sidebar menu to the template:</p>

<p>Yardbird/root/src/blog/index.tt:</p>

<pre><code>&lt;div class="sidebar_item"&gt;
  &lt;div class="sidebar_item_content"&gt;
    &lt;p&gt;&lt;strong&gt;Member Blogs&lt;/strong&gt;&lt;/p&gt;
    [% FOREACH member IN sorted_members %]
      &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', member.bid) %]"&gt;[% member.name %]&lt;/a&gt;&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>

<p>After adding the new sidebar menu we have:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH blog_entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
          &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Blogs&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH member IN sorted_members %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', member.bid) %]"&gt;[% member.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Run it and you should get a functional, decent looking sidebar menu with links to the most recent blog entries of all members:</p>

<p><img alt="yfc11f1.png" src="http://blogs.perl.org/users/j0e/yfc11f1.png" width="600" height="468" class="mt-image-none" style="" /></p>

<p><a name='a103'></a></p>

<h1>Summary</h1>

<p><a name='a203'></a></p>

<h2>Yardbird/lib/Yardbird.pm</h2>

<p>Configure to use AutoCRUD in the application class:</p>

<pre><code>use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple

  AutoCRUD

  Authentication
  Authorization::Roles

  Session
  Session::Store::File
  Session::State::Cookie 
/;
</code></pre>

<p>Use AutoCRUD when you run the application by going here:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>

<p><a name='a204'></a></p>

<h2>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm</h2>

<pre><code>package Yardbird::Schema::ResultSet::Blog;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific {
  my ($self, $bID) = @_;

  return $self-&gt;search({id =&gt; $bID});
} 

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, {order_by =&gt; {-desc =&gt; ['id']}, rows =&gt; 4});
} 

sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
} 

1;
</code></pre>

<p><a name='a205'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Blog.pm</h2>

<pre><code>package Yardbird::Controller::Blog;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);

  my @members;
  for my $user ($c-&gt;model('DB::User')-&gt;all) {
    my $blog_entry = $c-&gt;model('DB::Blog')-&gt;all_user($user-&gt;id)-&gt;first;
    push @members, { name =&gt; $user-&gt;name, bid =&gt; $blog_entry-&gt;id } if $blog_entry; 
  }
  my @sorted_members = sort { "\L$a-&gt;{name}" cmp "\L$b-&gt;{name}" } @members; 
  $c-&gt;stash(sorted_members =&gt; [@sorted_members]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a206'></a></p>

<h2>Yardbird/root/src/blog/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Blog'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Recent Blog Entries&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH blog_entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/index') _ '#bid' _ blog_entry.id %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt;&lt;/p&gt; 
          &lt;p class="sidebar_item_sub_subtitle"&gt;[% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;strong&gt;Member Blogs&lt;/strong&gt;&lt;/p&gt;
        [% FOREACH member IN sorted_members %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', member.bid) %]"&gt;[% member.name %]&lt;/a&gt;&lt;/p&gt;
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p class="content_title" id="[% 'bid' _ blog_entry.id %]"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt; 
      &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a207'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Blog/Entries.pm</h2>

<pre><code>package Yardbird::Controller::Blog::Entries;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(1) {
  my ($self, $c, $bID) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($bID)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);
  $c-&gt;detach($c-&gt;view("TT")); 
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='a208'></a></p>

<h2>Yardbird/root/src/blog/entries/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ blog_entry.userid.name %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;[% blog_entry.userid.name %]&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;The YARDBIRD Fan Club&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH entry IN blog_entries %]
          &lt;p class="sidebar_item_title"&gt;&lt;a href="[% c.uri_for_action('/blog/entries/index', entry.id) %]"&gt;[% entry.title %]&lt;/a&gt;&lt;/p&gt;
          &lt;p class="sidebar_item_subtitle"&gt;[% entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
        [% END %]
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;p class="content_title"&gt;[% blog_entry.title %]&lt;/p&gt;
    &lt;p class="content_subtitle"&gt;By &lt;strong&gt;[% blog_entry.userid.name %]&lt;/strong&gt; on [% blog_entry.created.strftime('%B %e, %Y %l:%M %p') %]&lt;/p&gt; 
    &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='a209'></a></p>

<h2>Yardbird/root/src/header.tt</h2>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('blog/index') %]"&gt;Blog&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          &lt;li&gt;Members&lt;/li&gt;
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;[% c.user.name %] | &lt;a href="[% c.uri_for_action('/logout/index') %]"&gt;Logout&lt;/a&gt;&lt;/li&gt;
          [% ELSE %]
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/li&gt; 
          [% END %]
        &lt;/div&gt;  
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p><a name='a210'></a></p>

<h2>Yardbird/root/static/css/main.css</h2>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}

#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}

.content_title {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0.3em;
}

.content_subtitle {
  font-size: 0.8em;
  margin-top: -1.6em;
}

.content_title a {
  color: #004080;
  text-decoration: none; 
}

.content_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_title {
  font-size: 0.9em;
  font-weight: bold;
}

.sidebar_item_title a {
  color: #004080;
  text-decoration: none; 
}

.sidebar_item_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_subtitle {
  font-size: 0.65em;
  margin-top: -1.5em;
}

.sidebar_item_sub_subtitle {
  font-size: 0.65em;
  margin-top: -1.0em;
}
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie 10: Authentication/Authorization</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4387</id>

    <published>2013-03-02T21:00:54Z</published>
    <updated>2013-03-23T22:42:04Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application....</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>

<p>Now that we've experimented a little and are confident in what we're doing, we'll start over from scratch, beginning with authentication and authorization as explained in the Catalyst tutorials.</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#100'>Prerequisites</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#101'>Create New Catalyst Application</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#200'>Create and Configure a TT View</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#102'>Authentication</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#201'>Create New Database</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#202'>Create and Configure a Model and Schema</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#300'>Include Authentication and Session Plugins</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#301'>Configure Authentication</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#302'>Modify the 'password' Column to Use PassphraseColumn</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#303'>Load Hashed Password into the Database</a></li>
</ul>

<p><li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#203'>Create New Controllers</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#204'>Add Feature: Login</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#205'>Add Feature: Logout</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#206'>Add Feature: Create New Member</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#304'>Not a Member?</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#305'>Join Now!</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#400'>HTML::FormHandler</a></li>
<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#500'>Create a Form</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#501'>Use the Form in a Controller</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#502'>Create a Template to View the Form</a></li>
</ul>

<p></ul>
</ul>
</ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#103'>Summary</a></li></p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#207'>Yardbird/lib/Yardbird.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#208'>Yardbird/Makefile.PL</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#209'>Yardbird/yardbird.sql</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#210'>Yardbird/set_hashed_passwords.pl</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#211'>Yardbird/lib/Yardbird/Controller/Login.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#212'>Yardbird/lib/Yardbird/Controller/Logout.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#213'>Yardbird/lib/Yardbird/Controller/Member.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#214'>Yardbird/lib/Yardbird/Controller/Root.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#215'>Yardbird/root/static/css/main.css</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#216'>Yardbird/root/src/footer.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#217'>Yardbird/root/src/header.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#218'>Yardbird/root/src/index.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#219'>Yardbird/root/src/login.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#220'>Yardbird/root/src/member/about.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#221'>Yardbird/root/src/member/create.tt</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#222'>Yardbird/lib/Yardbird/Form/User.pm</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html#223'>Yardbird/lib/Yardbird/Schema/Result/User.pm</a></li>
</ul>

<p></ul></p>
]]>
        <![CDATA[<p><a name='100'></a> </p>

<h1>Prerequisites</h1>

<p>Notes from a Newbie are not an exhaustive, authoritative source for information, they augment Catalyst tutorials and other documentation and resources you may be using. Therefore you must study and be competent with the Catalyst tutorials and other documents mentioned in <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#201' target='_blank'>Experiment 01</a> for <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html' target='_blank'>Notes from a Newbie</a> to be of use to you.</p>

<p>People at irc.perl.org have been very generous and helpful in answering my questions, I see no reason for them not to do the same for you. Lurk about the catalyst channel at irc.perl.org and ask questions. mst's bite can sting a little so be careful, don't be a fool - but don't be too cool for school.</p>

<p>And please go now, right away, before you forget, sign-up and become a member of <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a>, and be kind to each other.</p>

<p>Experiment! Create and edit your own blog entries, comments and personal information.</p>

<p><a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> is there for you to see how it works, please become a member and say hello.</p>

<p>As a member, you will be able to find my email address in the webmaster's personal information. I would be happy to correspond with you about Catalyst.</p>

<p>You may also correspond via the <a href='http://yardbirdfanclub.org/blog' target='_blank'>yardbirdfanclub.org blog</a>, I am watching it closely.</p>

<p><a name='101'></a> </p>

<h1>Create New Catalyst Application</h1>

<p>As I said previously, now that we've experimented a little we will start over from scratch. We begin by creating a new Catalyst application in a new directory as we did in our experiments:</p>

<pre><code>&gt; catalyst.pl Yardbird 

Yardbird&gt; perl Makefile.PL
</code></pre>

<p><a name='200'></a> </p>

<h2>Create and Configure a TT View</h2>

<pre><code>Yardbird&gt; script/yardbird_create.pl view TT TT
</code></pre>

<p>In our experiment we used the default location for our templates, but I now want to configure the application to use my templates in a different location and for 'TT' to be my default view:</p>

<p>Yardbird/lib/Yardbird.pm: </p>

<pre><code>__PACKAGE__-&gt;config(
  # Configure the view
  'View::TT' =&gt; {
    #Set the location for TT files
    INCLUDE_PATH =&gt; [
      __PACKAGE__-&gt;path_to( 'root', 'src' ),
    ],
  },
  default_view =&gt; 'TT',
);
</code></pre>

<p>In our new application we can use the header.tt and footer.tt templates, and main.css stylesheet from our 03 experiment, they don't require any changes:</p>

<p>Yardbird/root/src/header.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Blog&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          &lt;li&gt;Members&lt;/li&gt;
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          &lt;li&gt;Login | Not a member?&lt;/li&gt; 
        &lt;/div&gt;
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p>Yardbird/root/src/footer.tt:</p>

<pre><code>    &lt;div id="footer"&gt;
      &lt;p&gt;Feedback Welcome | Webmaster&lt;span style="float:right"&gt;The YARDBIRD Fan Club&lt;/span&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt; &lt;!-- wrapper --&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>root/static/css/main.css:</p>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}
</code></pre>

<p>You must also copy your image file of Bird from your 03 experiment and save it in your new app:</p>

<pre><code>Yardbird/root/static/images/bird/bird.gif
</code></pre>

<p>Go to the <a href='http://yardbirdfanclub.org/blog' target='_blank'>blog section of yardbirdfanclub.org</a> if you need a copy of the file, and right-click on the image of Bird (he will have a frisbee in his mouth) and save. </p>

<p>Copy index.tt from your 03 experiment, modify it to not use a database, update your new controller to use index.tt, run the application and see if you get anything decent.</p>

<p>Yardbird/root/src/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
    &lt;h3&gt;Homepage&lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Yardbird/lib/Yardbird/Controller/Root.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>We now have the following files in our new application:</p>

<p><img alt="yfc10b.png" src="http://blogs.perl.org/users/j0e/yfc10b.png" width="120" height="367" class="mt-image-none" style="" /></p>

<p>Run it and you should get a decent page:</p>

<pre><code>Yardbird&gt; script/yardbird_server.pl -r
</code></pre>

<p><a name='102'></a> </p>

<h1>Authentication</h1>

<p>We now have a decent page and will begin to add features.</p>

<p><img alt="yfc10a.png" src="http://blogs.perl.org/users/j0e/yfc10a.png" width="575" height="183" class="mt-image-none" style="" /></p>

<p>We will need a way for people to become new members, and for members to be able to login and logout.</p>

<p>Logged-in users will be able to:</p>

<ul>
<li>Create, edit and delete their own blog entries and comments.</li>
<li>Determine what information about themselves they want other members to be able to view. </li>
<li>View other member's personal information.</li>
</ul>

<p>Non-members will be unable to:</p>

<ul>
<li>Create blog entries or comments.</li>
<li>Provide personal information to share with other members.</li>
<li>View other members personal information.</li>
</ul>

<p><a name='201'></a> </p>

<h2>Create New Database</h2>

<p>Let's begin by creating a new database. The following yardbird.sql file will generate all tables and columns needed for yardbirdfanclub.org.</p>

<p>yardbird.sql:</p>

<pre><code>--
-- Add user and role tables, along with a many-to-many join table
--
CREATE TABLE `user` (
  id            INT UNSIGNED NOT NULL AUTO_INCREMENT,
  username      VARCHAR(30) NOT NULL,
  password      TEXT NOT NULL,
  email         VARCHAR(50) NOT NULL,
  email_visible INT UNSIGNED,
  firstname     VARCHAR(30),
  lastname      VARCHAR(30),
  location      VARCHAR(100),
  about_me      TEXT,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE role (
  id          INT UNSIGNED NOT NULL AUTO_INCREMENT,
  -- Careful, don't create role's larger than 28 characters to be safe.
  role        VARCHAR(30) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE user_role (
  userid INT UNSIGNED NOT NULL,
  roleid INT UNSIGNED NOT NULL,
  -- Delete user_role record or update userid when `user`(id) is deleted or updated.
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (roleid) REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (userid, roleid)
) ENGINE=InnoDB;
--
-- Add blog and blog_comment tables
--
CREATE TABLE blog (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  userid  INT UNSIGNED NOT NULL,
  title   TEXT NOT NULL,
  content TEXT NOT NULL,
  created TIMESTAMP NOT NULL,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE blog_comment (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  blogid  INT UNSIGNED NOT NULL,
  -- userid of the commenter (NOT THE userid OF THE BLOG ENTRY)
  userid  INT UNSIGNED NOT NULL,
  content TEXT NOT NULL,
  created TIMESTAMP NOT NULL,
  FOREIGN KEY (blogid) REFERENCES blog(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id) 
) ENGINE=InnoDB;
INSERT INTO `user` (id, username, email) VALUES (1, 'webmaster', 'webmaster@something.com');
INSERT INTO role (id, role) VALUES (1, 'user');
INSERT INTO role (id, role) VALUES (2, 'admin');
INSERT INTO user_role (userid, roleid) VALUES (1, 1); -- Assign user role to webmaster
INSERT INTO user_role (userid, roleid) VALUES (1, 2); -- Assign admin role to webmaster
INSERT INTO blog (userid, title, content) VALUES (1, 'title', 'content');
</code></pre>

<p>We are creating one user with <code>id</code> 1 and <code>username</code> webmaster. We also assign both <code>user</code> and <code>admin</code> roles and create a simple blog entry for webmaster.</p>

<p>The <code>user</code>, <code>role</code> and <code>user_role</code> tables are explained in the Catalyst tutorials:</p>

<ul>
<li>Catalyst::Manual::Tutorial::05_Authentication</li>
<li>Catalyst::Manual::Tutorial::06_Authorization</li>
</ul>

<p>Our <code>user</code> table contains a few columns not used in the Catalyst tutorials: </p>

<ul>
<li><code>email_visible</code>: Make email address visible to other members. </li>
<li><code>location</code>: Whatever a user wants to describe where they live.</li>
<li><code>about_me</code>: Whatever a user wants other members to know about themselves.</li>
</ul>

<p>The <code>blog</code> and <code>blog_comment</code> tables are the same as we used in our experiments, with the addition of a <code>created</code> column to store a blog entry or comment creation time and date.</p>

<p>Use yardbird.sql to create a new database:</p>

<pre><code>&gt; mysql -uusername -ppassword

mysql&gt; create database yardbird;

mysql&gt; quit;

&gt; mysql -uusername -ppassword yardbird &lt; yardbird.sql
</code></pre>

<p><a name='202'></a> </p>

<h2>Create and Configure a Model and Schema</h2>

<p>Run the following command to create a model and schema for our application:</p>

<pre><code>Yardbird&gt; script/yardbird_create.pl model DB DBIC::Schema Yardbird::Schema create=static components=TimeStamp,PassphraseColumn dbi:mysql:yardbird username password
</code></pre>

<p>Note: There is no space between the comma and PassphraseColumn in the above command.</p>

<p>Our application should now have new Model and Schema classes:</p>

<p><img alt="yfc10c.png" src="http://blogs.perl.org/users/j0e/yfc10c.png" width="140" height="195" class="mt-image-none" style="" /></p>

<p><a name='300'></a> </p>

<h3>Include Authentication and Session Plugins</h3>

<p>Update the application class to use the following:</p>

<pre><code>Authentication
Authorization::Roles

Session
Session::Store::File
Session::State::Cookie
</code></pre>

<p>I have this after adding the above:</p>

<p>Yardbird/lib/Yardbird.pm:</p>

<pre><code>use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple

  Authentication
  Authorization::Roles

  Session
  Session::Store::File
  Session::State::Cookie 
/;
</code></pre>

<p>Update Makefile.PL to use the following:</p>

<p>Yardbird/Makefile.PL:</p>

<pre><code>requires 'Catalyst::Plugin::Authentication';
requires 'Catalyst::Plugin::Authorization::Roles';
requires 'Catalyst::Plugin::Session';
requires 'Catalyst::Plugin::Session::Store::File';
requires 'Catalyst::Plugin::Session::State::Cookie';
</code></pre>

<p><a name='301'></a></p>

<h3>Configure Authentication</h3>

<p>We use Catalyst::Authentication::Realm::SimpleDB because it automatically sets a reasonable set of defaults for us.</p>

<p>Add the following before <code>__PACKAGE__-&gt;setup();</code> in the application class to configure SimpleDB Authentication:</p>

<p>Yardbird/lib/Yardbird.pm:</p>

<pre><code># Configure SimpleDB Authentication
__PACKAGE__-&gt;config(
    'Plugin::Authentication' =&gt; {
        default =&gt; {
            class           =&gt; 'SimpleDB',
            user_model      =&gt; 'DB::User',
            password_type   =&gt; 'self_check',
        },
    },
);
</code></pre>

<p><a name='302'></a></p>

<h3>Modify the 'password' Column to Use PassphraseColumn</h3>

<p>Add to the user Result class below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line, but above the closing "1;": </p>

<p>Yardbird/lib/Yardbird/Schema/Result/User.pm:</p>

<pre><code># Have the 'password' column use a SHA-1 hash and 20-byte salt
# with RFC 2307 encoding; Generate the 'check_password" method
__PACKAGE__-&gt;add_columns(
    'password' =&gt; {
        passphrase       =&gt; 'rfc2307',
        passphrase_class =&gt; 'SaltedDigest',
        passphrase_args  =&gt; {
            algorithm   =&gt; 'SHA-1',
            salt_random =&gt; 20.
        },
        passphrase_check_method =&gt; 'check_password',
    },
);
</code></pre>

<p><a name='303'></a></p>

<h3>Load Hashed Password into the Database</h3>

<p>Initialize a hashed password for webmaster with the following script:</p>

<p>Yardbird/set<em>hashed</em>passwords.pl:</p>

<pre><code>#!/usr/bin/perl
use strict;
use warnings;
use Yardbird::Schema;

# $ perl -Ilib set_hashed_passwords.pl 

my $schema = Yardbird::Schema-&gt;connect('dbi:mysql:yardbird', 'username', 'password');
my @users = $schema-&gt;resultset('User')-&gt;all;
foreach my $user (@users) {
    $user-&gt;password('password');
    $user-&gt;update;
}
</code></pre>

<p>Run the script:</p>

<pre><code>Yardbird&gt; perl -Ilib set_hashed_passwords.pl
</code></pre>

<p>We should be done creating and configuring the Model and Schema now. As a sanity check you may want to run the application and verify you still get the same decent page you had previously:</p>

<pre><code>Yardbird&gt; script/yardbird_server.pl -r
</code></pre>

<p><a name='203'></a> </p>

<h2>Create New Controllers</h2>

<p>Create three new controllers for logging in and out and creating new members:</p>

<pre><code>Yardbird&gt; script/yardbird_create.pl controller Login
Yardbird&gt; script/yardbird_create.pl controller Logout 
Yardbird&gt; script/yardbird_create.pl controller Member
</code></pre>

<p><a name='204'></a> </p>

<h2>Add Feature: Login</h2>

<p>Replace the index method in the new Login controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Login.pm:</p>

<pre><code>sub index :Path :Args(0) {
    my ($self, $c) = @_;

    # Get the username and password from form
    my $username = $c-&gt;request-&gt;params-&gt;{username};
    my $password = $c-&gt;request-&gt;params-&gt;{password};

    # If the username and password values were found in form
    if ($username &amp;&amp; $password) {
        # Attempt to log the user in
        if ($c-&gt;authenticate({ username =&gt; $username,
                               password =&gt; $password  } )) {
            # If successful, then let them use the application
            $c-&gt;response-&gt;redirect($c-&gt;uri_for_action('/index'));
            return;
        } else {
            # Set an error message
            $c-&gt;stash(error_msg =&gt; "Bad username or password.");
        }
    } else {
        # Set an error message
        $c-&gt;stash(error_msg =&gt; "Empty username or password.")
            unless ($c-&gt;user_exists);
    }

    # If either of above don't work out, send to the login page
    $c-&gt;stash(template =&gt; 'login.tt');
}
</code></pre>

<p>Create a Login template for the view:</p>

<p>Yardbird/root/src/login.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Login'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
    &lt;h3&gt;Login&lt;/h3&gt;

    &lt;form method="post" action="[% c.uri_for_action('/login/index') %]"&gt;
      &lt;table&gt;
        &lt;tr&gt;
          &lt;td&gt;Username:&lt;/td&gt;
          &lt;td&gt;&lt;input type="text" name="username" size="40" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;Password:&lt;/td&gt;
          &lt;td&gt;&lt;input type="password" name="password" size="40" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;         &lt;/td&gt;
          &lt;td colspan="2"&gt;&lt;input type="submit" name="submit" value="Submit" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/table&gt;
    &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>We need to modify header.tt to link to our new Login and Logout controllers.</p>

<p>Replace this:</p>

<p>Yardbird/root/src/header.tt:</p>

<pre><code>&lt;div id="main_menu_right"&gt;
  &lt;li&gt;Login | Not a member?&lt;/li&gt; 
&lt;/div&gt;
</code></pre>

<p>With this:</p>

<pre><code>&lt;div id="main_menu_right"&gt;
  [% IF c.user_exists %] 
    &lt;li&gt;[% c.user.name %] | &lt;a href="[% c.uri_for_action('/logout/index') %]"&gt;Logout&lt;/a&gt;&lt;/li&gt;
  [% ELSE %]
    &lt;li&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login&lt;/a&gt; | Not a member?&lt;/li&gt; 
  [% END %]
&lt;/div&gt;
</code></pre>

<p>header.tt needs the user Result <code>name</code> method we created in the experiments. </p>

<p>Add the following below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line, but above the closing "1;": </p>

<p>Yardbird/lib/Yardbird/Schema/Result/User.pm:</p>

<pre><code>sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}
</code></pre>

<p>Go to <a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#205' target='_blank'>Experiment 02</a> and refresh your memory about Result class methods if you need to.</p>

<p><a name='205'></a> </p>

<h2>Add Feature: Logout</h2>

<p>Replace the index method in the new Logout controller:</p>

<p>Yardbird/lib/Yardbird/Controller/Logout.pm:</p>

<pre><code>sub index :Path :Args(0) {
    my ($self, $c) = @_;

    # Clear the user's state
    $c-&gt;logout;

    # Send the user to the starting point
    $c-&gt;response-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p>If we've done everything correctly, we should now be able to log the webmaster in and out with 'password'.</p>

<p><img alt="yfc10d.png" src="http://blogs.perl.org/users/j0e/yfc10d.png" width="575" height="182" class="mt-image-none" style="" /></p>

<p><a name='206'></a> </p>

<h2>Add Feature: Create New Member</h2>

<p><a name='304'></a> </p>

<h3>Not a Member?</h3>

<p>If you are following along you may want to go to <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> and take the 'Not a Member?' link to see how a new member is created. When a user selects this feature, the following reasons for joining appear with a Sidebar Menu 'Join now!' link:</p>

<ul>
<li>Post blog entries and comments.</li>
<li>Optionally provide your contact and other information to other users.</li>
<li>View other member's contact information. </li>
</ul>

<p>Add method to Member controller:</p>

<p>Controller/Member.pm:</p>

<pre><code>sub about :Local :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Notice the use of the <code>:Local</code> attribute.</p>

<p>Previously we've used the <code>:Path</code> attribute in all our controller actions, which achieves the same results with a little more typing:</p>

<pre><code>sub about :Path('about') :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p><code>:Path</code> actions let you map a method to an explicit URI path.</p>

<p>For example, <code>:Path('create')</code> in lib/Yardbird/Controller/Member.pm would match on http://localhost:3000/member/create, but <code>:Path('/create')</code> would match on http://localhost:3000/create because of the leading slash. </p>

<p><code>:Local</code> is shorthand for <code>:Path('name_of_method')</code>.</p>

<p>For example, these are equivalent: <code>sub create :Local {...}</code> and <code>sub create :Path('create') {...}</code>. </p>

<p>See:</p>

<ul>
<li>Catalyst::Manual::Tutorial::03_MoreCatalystBasics</li>
</ul>

<p>root/src/member/about.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Membership' %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;Join now!&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Why become a member?&lt;/h3&gt;
    &lt;p&gt;With membership you can:
    &lt;ul&gt;
      &lt;li&gt;Post blog entries and comments.&lt;/li&gt;
      &lt;li&gt;Optionally provide your contact and other information to other users.&lt;/li&gt;
      &lt;li&gt;View other member's contact information.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Modify header.tt to link to member/about.</p>

<p>Change this:</p>

<p>root/src/header.tt:</p>

<pre><code>Not a member?
</code></pre>

<p>To this:</p>

<pre><code>&lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;
</code></pre>

<p>Have you noticed any relationships between controller/action names/paths, template names/paths and URI's?</p>

<p>Run the application and you should see our new 'Not a member?' link and 'Join now!' item in a Sidebar Menu:</p>

<p><img alt="yfc10e.png" src="http://blogs.perl.org/users/j0e/yfc10e.png" width="600" height="255" class="mt-image-none" style="" /></p>

<p><a name='305'></a> </p>

<h3>Join Now!</h3>

<p>Finally, we add the ability for <a href='http://yardbirdfanclub.org' target='_blank'>yardbirdfanclub.org</a> users to become members of The Yardbird Fanclub.</p>

<p>I used HTML::FormHandler to validate and process data.</p>

<p><a name='400'></a> </p>

<h4>HTML::FormHandler</h4>

<p>I learned how to use HTML::FormHandler from these docs:</p>

<ul>
<li>HTML::FormHandler</li>
<li>HTML::FormHandler::Manual::Tutorial</li>
<li>HTML::FormHandler::Manual::Catalyst</li>
<li>HTML::FormHandler::Manual::Intro </li>
</ul>

<p><a name='500'></a> </p>

<h5>Create a Form</h5>

<p>We will create a form for inputting new member information.</p>

<p>Yardbird/lib/Yardbird/Form/User.pm:</p>

<pre><code>package Yardbird::Form::User;
use HTML::FormHandler::Moose;
use HTML::FormHandler::Types ('NoSpaces', 'WordChars', 'NotAllDigits', 'SimpleStr' ); 
extends 'HTML::FormHandler::Model::DBIC';

has '+item_class' =&gt; ( default =&gt; 'User' );

has_field 'username' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  required =&gt; 1,
  unique =&gt; 1,
  maxlength =&gt; 25,
);
has_field 'password' =&gt; (
  type =&gt; 'Password',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  required =&gt; 1,
  maxlength =&gt; 25,
);
has_field 'password_confirm' =&gt; (
  type =&gt; 'PasswordConf',
  tags =&gt; { label_after =&gt; ': ' }, 
);
has_field 'email' =&gt; (
  type  =&gt; 'Email',
  required =&gt; 1,
  unique =&gt; 1,
  maxlength =&gt; 45,
);
has_field 'email_visible' =&gt; (
  type  =&gt; 'Checkbox',
);
has_field 'firstname' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  maxlength =&gt; 25,
);
has_field 'lastname' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  maxlength =&gt; 25,
);
has_field 'location' =&gt; (
  type =&gt; 'Text',
  maxlength =&gt; 95,
);
has_field 'about_me' =&gt; (
  type =&gt; 'TextArea',
  cols =&gt; 70,
  rows =&gt; 10,
  do_label =&gt; 0, 
);
has_field 'submit' =&gt; (
  type =&gt; 'Submit',
  value =&gt; 'Submit',
);

no HTML::FormHandler::Moose;
1;
</code></pre>

<p><a name='501'></a> </p>

<h5>Use the Form in a Controller</h5>

<p>In the controller you should now have:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }
</code></pre>

<p>Edit the controller to use your new form:</p>

<p>Yardbird/lib/Yardbird/Controller/Member.pm:</p>

<pre><code>package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;
use Yardbird::Form::User; 

BEGIN { extends 'Catalyst::Controller'; }

has 'form' =&gt; (
  isa =&gt; 'Yardbird::Form::User',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::User-&gt;new }
);
</code></pre>

<p>Create a controller action for creating a new member:</p>

<pre><code>sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated, return to home
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}
</code></pre>

<p><a name='502'></a> </p>

<h5>Create a Template to View the Form</h5>

<p>Yardbird/root/src/member/create.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Join The YARDBIRD Fan Club&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/&gt;
    &lt;/form&gt; 

    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p>Notice above, we use three new styles we must add to our stylesheet.</p>

<h5>Update our stylesheet:</h5>

<p>Yardbird/root/static/css/main.css:</p>

<pre><code>#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}
</code></pre>

<p>Finally, we can enable a link for creating a new member.</p>

<h5>Enable a link:</h5>

<p>root/src/member/about.tt:</p>

<pre><code>&lt;p&gt;&lt;a href="[% c.uri_for_action('/member/create') %]"&gt;Join now!&lt;/a&gt;&lt;/p&gt;
</code></pre>

<p>Run it and you should be able to create a new member with a decent looking page:</p>

<p><img alt="yfc10f.png" src="http://blogs.perl.org/users/j0e/yfc10f.png" width="600" height="680" class="mt-image-none" style="" /></p>

<p><a name='103'></a> </p>

<h1>Summary</h1>

<p><a name='207'></a> </p>

<h2>Yardbird/lib/Yardbird.pm</h2>

<pre><code>package Yardbird;
use Moose;
use namespace::autoclean;

use Catalyst::Runtime 5.80;

use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple

  Authentication
  Authorization::Roles

  Session
  Session::Store::File
  Session::State::Cookie 
/;

extends 'Catalyst';

our $VERSION = '0.01';

__PACKAGE__-&gt;config(
    name =&gt; 'Yardbird',
    # Disable deprecated behavior needed by old applications
    disable_component_resolution_regex_fallback =&gt; 1,
    enable_catalyst_header =&gt; 1, # Send X-Catalyst header
);

__PACKAGE__-&gt;config(
  # Configure the view
  'View::TT' =&gt; {
    #Set the location for TT files
    INCLUDE_PATH =&gt; [
      __PACKAGE__-&gt;path_to( 'root', 'src' ),
    ],
  },
  default_view =&gt; 'TT',
);  

# Configure SimpleDB Authentication
__PACKAGE__-&gt;config(
    'Plugin::Authentication' =&gt; {
        default =&gt; {
            class           =&gt; 'SimpleDB',
            user_model      =&gt; 'DB::User',
            password_type   =&gt; 'self_check',
        },
    },
);

# Start the application
__PACKAGE__-&gt;setup();

1;
</code></pre>

<p><a name='208'></a> </p>

<h2>Yardbird/Makefile.PL</h2>

<pre><code>#!/usr/bin/env perl
# IMPORTANT: if you delete this file your app will not work as
# expected.  You have been warned.
use inc::Module::Install 1.02;
use Module::Install::Catalyst; # Complain loudly if you don't have
                               # Catalyst::Devel installed or haven't said
                               # 'make dist' to create a standalone tarball.

name 'Yardbird';
all_from 'lib/Yardbird.pm';

requires 'Catalyst::Runtime' =&gt; '5.90018';
requires 'Catalyst::Plugin::ConfigLoader';
requires 'Catalyst::Plugin::Static::Simple';
requires 'Catalyst::Action::RenderView';
requires 'Moose';
requires 'namespace::autoclean';
requires 'Config::General'; # This should reflect the config file format you've chosen
                 # See Catalyst::Plugin::ConfigLoader for supported formats

requires 'Catalyst::Plugin::Authentication';
requires 'Catalyst::Plugin::Authorization::Roles';
requires 'Catalyst::Plugin::Session';
requires 'Catalyst::Plugin::Session::Store::File';
requires 'Catalyst::Plugin::Session::State::Cookie';

test_requires 'Test::More' =&gt; '0.88';
catalyst;

install_script glob('script/*.pl');
auto_install;
WriteAll;
</code></pre>

<p><a name='209'></a> </p>

<h2>Yardbird/yardbird.sql</h2>

<pre><code>--
-- Add user and role tables, along with a many-to-many join table
--
CREATE TABLE `user` (
  id            INT UNSIGNED NOT NULL AUTO_INCREMENT,
  username      VARCHAR(30) NOT NULL,
  password      TEXT NOT NULL,
  email         VARCHAR(50) NOT NULL,
  email_visible INT UNSIGNED,
  firstname     VARCHAR(30),
  lastname      VARCHAR(30),
  location      VARCHAR(100),
  about_me      TEXT,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE role (
  id          INT UNSIGNED NOT NULL AUTO_INCREMENT,
  -- Careful, don't create role's larger than 28 characters to be safe.
  role        VARCHAR(30) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE user_role (
  userid INT UNSIGNED NOT NULL,
  roleid INT UNSIGNED NOT NULL,
  -- Delete user_role record or update userid when `user`(id) is deleted or updated.
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (roleid) REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (userid, roleid)
) ENGINE=InnoDB;
--
-- Add blog and blog_comment tables
--
CREATE TABLE blog (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  userid  INT UNSIGNED NOT NULL,
  title   TEXT NOT NULL,
  content TEXT NOT NULL,
  created TIMESTAMP NOT NULL,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE blog_comment (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  blogid  INT UNSIGNED NOT NULL,
  -- userid of the commenter (NOT THE userid OF THE BLOG ENTRY)
  userid  INT UNSIGNED NOT NULL,
  content TEXT NOT NULL,
  created TIMESTAMP NOT NULL,
  FOREIGN KEY (blogid) REFERENCES blog(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id) 
) ENGINE=InnoDB;
INSERT INTO `user` (id, username, email) VALUES (1, 'webmaster', 'webmaster@something.com');
INSERT INTO role (id, role) VALUES (1, 'user');
INSERT INTO role (id, role) VALUES (2, 'admin');
INSERT INTO user_role (userid, roleid) VALUES (1, 1); -- Assign user role to webmaster
INSERT INTO user_role (userid, roleid) VALUES (1, 2); -- Assign admin role to webmaster
INSERT INTO blog (userid, title, content) VALUES (1, 'title', 'content');
</code></pre>

<h3>Create Model/Schema:</h3>

<pre><code>Yardbird&gt; script/yardbird_create.pl model DB DBIC::Schema Yardbird::Schema create=static components=TimeStamp,PassphraseColumn dbi:mysql:yardbird username password
</code></pre>

<p><a name='210'></a></p>

<h2><code>Yardbird/set_hashed_passwords.pl</code></h2>

<pre><code>#!/usr/bin/perl
use strict;
use warnings;
use Yardbird::Schema;

# $ perl -Ilib set_hashed_passwords.pl 

my $schema = Yardbird::Schema-&gt;connect('dbi:mysql:yardbird', 'username', 'password');
my @users = $schema-&gt;resultset('User')-&gt;all;
foreach my $user (@users) {
    $user-&gt;password('password');
    $user-&gt;update;
}
</code></pre>

<p><a name='211'></a></p>

<h2>Yardbird/lib/Yardbird/Controller/Login.pm</h2>

<pre><code>package Yardbird::Controller::Login;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(0) {
    my ($self, $c) = @_;

    # Get the username and password from form
    my $username = $c-&gt;request-&gt;params-&gt;{username};
    my $password = $c-&gt;request-&gt;params-&gt;{password};

    # If the username and password values were found in form
    if ($username &amp;&amp; $password) {
        # Attempt to log the user in
        if ($c-&gt;authenticate({ username =&gt; $username,
                               password =&gt; $password  } )) {
            # If successful, then let them use the application
            $c-&gt;response-&gt;redirect($c-&gt;uri_for_action('/index'));
            return;
        } else {
            # Set an error message
            $c-&gt;stash(error_msg =&gt; "Bad username or password.");
        }
    } else {
        # Set an error message
        $c-&gt;stash(error_msg =&gt; "Empty username or password.")
            unless ($c-&gt;user_exists);
    }

    # If either of above don't work out, send to the login page
    $c-&gt;stash(template =&gt; 'login.tt');
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='212'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Logout.pm</h2>

<pre><code>package Yardbird::Controller::Logout;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(0) {
    my ($self, $c) = @_;

    # Clear the user's state
    $c-&gt;logout;

    # Send the user to the starting point
    $c-&gt;response-&gt;redirect($c-&gt;uri_for_action('/index'));
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='213'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Member.pm</h2>

<pre><code>package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;
use Yardbird::Form::User; 

BEGIN { extends 'Catalyst::Controller'; }

has 'form' =&gt; (
  isa =&gt; 'Yardbird::Form::User',
  is =&gt; 'rw',
  lazy =&gt; 1,
  default =&gt; sub { Yardbird::Form::User-&gt;new }
);

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched Yardbird::Controller::Member in Member.');
}

sub about :Local :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c-&gt;stash(template =&gt; 'member/create.tt', form =&gt; $self-&gt;form );

  # Validate and insert data into database
  return unless $self-&gt;form-&gt;process(
    item_id =&gt; $user_id,
    params =&gt; $c-&gt;req-&gt;parameters,
    schema =&gt; $c-&gt;model('DB')-&gt;schema
  );

  # Form validated, return to home
  $c-&gt;res-&gt;redirect($c-&gt;uri_for_action('/index'));
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='214'></a> </p>

<h2>Yardbird/lib/Yardbird/Controller/Root.pm</h2>

<pre><code>package Yardbird::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }

#
# Sets the actions in this controller to be registered with no prefix
# so they function identically to actions created in MyApp.pm
#
__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub default :Path {
    my ( $self, $c ) = @_;
    $c-&gt;response-&gt;body( 'Page not found' );
    $c-&gt;response-&gt;status(404);
}


sub end : ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p><a name='215'></a> </p>

<h2>Yardbird/root/static/css/main.css</h2>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}

#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}
</code></pre>

<p><a name='216'></a> </p>

<h2>Yardbird/root/src/footer.tt</h2>

<pre><code>    &lt;div id="footer"&gt;
      &lt;p&gt;Feedback Welcome | Webmaster&lt;span style="float:right"&gt;The YARDBIRD Fan Club&lt;/span&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt; &lt;!-- wrapper --&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p><a name='217'></a> </p>

<h2>Yardbird/root/src/header.tt</h2>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Blog&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          &lt;li&gt;Members&lt;/li&gt;
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          [% IF c.user_exists %] 
            &lt;li&gt;[% c.user.name %] | &lt;a href="[% c.uri_for_action('/logout/index') %]"&gt;Logout&lt;/a&gt;&lt;/li&gt;
          [% ELSE %]
            &lt;li&gt;&lt;a href="[% c.uri_for_action('/login/index') %]"&gt;Login&lt;/a&gt; | &lt;a href="[% c.uri_for_action('/member/about') %]"&gt;Not a member?&lt;/a&gt;&lt;/li&gt; 
          [% END %]
        &lt;/div&gt;  
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p><a name='218'></a> </p>

<h2>Yardbird/root/src/index.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
    &lt;h3&gt;Homepage&lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='219'></a> </p>

<h2>Yardbird/root/src/login.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Login'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
    &lt;h3&gt;Login&lt;/h3&gt;

    &lt;form method="post" action="[% c.uri_for_action('/login/index') %]"&gt;
      &lt;table&gt;
        &lt;tr&gt;
          &lt;td&gt;Username:&lt;/td&gt;
          &lt;td&gt;&lt;input type="text" name="username" size="40" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;Password:&lt;/td&gt;
          &lt;td&gt;&lt;input type="password" name="password" size="40" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;         &lt;/td&gt;
          &lt;td colspan="2"&gt;&lt;input type="submit" name="submit" value="Submit" /&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/table&gt;
    &lt;/form&gt; 

  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='220'></a> </p>

<h2>Yardbird/root/src/member/about.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Membership' %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;
    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        &lt;p&gt;&lt;a href="[% c.uri_for_action('/member/create') %]"&gt;Join now!&lt;/a&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Why become a member?&lt;/h3&gt;
    &lt;p&gt;With membership you can:
    &lt;ul&gt;
      &lt;li&gt;Post blog entries and comments.&lt;/li&gt;
      &lt;li&gt;Optionally provide your contact and other information to other users.&lt;/li&gt;
      &lt;li&gt;View other member's contact information.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='221'></a></p>

<h2>Yardbird/root/src/member/create.tt</h2>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;Join The YARDBIRD Fan Club&lt;/h3&gt;

    &lt;form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"&gt; 
        [% f = form.field('username') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('password') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('password_confirm') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="password" name="[% f.name %]" id="[% f.name %]" value=""&gt; 
      &lt;br&gt;
        [% f = form.field('email') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
        [% f = form.field('email_visible') %]
        &lt;input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"&gt; 
        &lt;label for="[% f.name %]"&gt;Visible&lt;/label&gt;
      &lt;br&gt;
        [% f = form.field('firstname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('lastname') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        [% f = form.field('location') %]
        &lt;label class="label" for="[% f.name %]"&gt;[% f.label %]:&lt;/label&gt;
        &lt;input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"&gt; 
      &lt;br&gt;
        About me:
        &lt;div id='user_form_textarea'&gt;
        [% form.field('about_me').render %]
        &lt;/div&gt;
      &lt;input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/&gt;
    &lt;/form&gt; 

    &lt;p&gt;Username and Password are required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Username must be unique among member usernames.&lt;/li&gt;
      &lt;li&gt;They may contain upper and lowercase letters, digits, and the underscore character.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;A valid, unique email address is required.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If "Email visible" is checked, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Firstname and Lastname are optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If either are provided, they will be used in page content and visible to both members and non-members alike.&lt;/li&gt;
      &lt;li&gt;If neither are provided, your Username will be used in-lieu of them.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Location is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;"About me" is optional.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If provided, it will be visible only to members via the Members page.&lt;/li&gt;
      &lt;li&gt;HTML Tags may be used for styling.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='222'></a> </p>

<h2>Yardbird/lib/Yardbird/Form/User.pm</h2>

<pre><code>package Yardbird::Form::User;
use HTML::FormHandler::Moose;
use HTML::FormHandler::Types ('NoSpaces', 'WordChars', 'NotAllDigits', 'SimpleStr' ); 
extends 'HTML::FormHandler::Model::DBIC';

has '+item_class' =&gt; ( default =&gt; 'User' );

has_field 'username' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  required =&gt; 1,
  unique =&gt; 1,
  maxlength =&gt; 25,
);
has_field 'password' =&gt; (
  type =&gt; 'Password',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  required =&gt; 1,
  maxlength =&gt; 25,
);
has_field 'password_confirm' =&gt; (
  type =&gt; 'PasswordConf',
  tags =&gt; { label_after =&gt; ': ' }, 
);
has_field 'email' =&gt; (
  type  =&gt; 'Email',
  required =&gt; 1,
  unique =&gt; 1,
  maxlength =&gt; 45,
);
has_field 'email_visible' =&gt; (
  type  =&gt; 'Checkbox',
);
has_field 'firstname' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  maxlength =&gt; 25,
);
has_field 'lastname' =&gt; (
  type =&gt; 'Text',
  apply =&gt; [ NoSpaces, WordChars, NotAllDigits ], 
  maxlength =&gt; 25,
);
has_field 'location' =&gt; (
  type =&gt; 'Text',
  maxlength =&gt; 95,
);
has_field 'about_me' =&gt; (
  type =&gt; 'TextArea',
  cols =&gt; 70,
  rows =&gt; 10,
  do_label =&gt; 0, 
);
has_field 'submit' =&gt; (
  type =&gt; 'Submit',
  value =&gt; 'Submit',
);

no HTML::FormHandler::Moose;
1;
</code></pre>

<p><a name='223'></a> </p>

<h2>Yardbird/lib/Yardbird/Schema/Result/User.pm</h2>

<pre><code>use utf8;
package Yardbird::Schema::Result::User;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE

=head1 NAME

Yardbird::Schema::Result::User

=cut

use strict;
use warnings;

use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean =&gt; 1;
extends 'DBIx::Class::Core';

=head1 COMPONENTS LOADED

=over 4

=item * L&lt;DBIx::Class::InflateColumn::DateTime&gt;

=item * L&lt;DBIx::Class::TimeStamp&gt;

=item * L&lt;DBIx::Class::PassphraseColumn&gt;

=back

=cut

__PACKAGE__-&gt;load_components("InflateColumn::DateTime", "TimeStamp", "PassphraseColumn");

=head1 TABLE: C&lt;user&gt;

=cut

__PACKAGE__-&gt;table("user");

=head1 ACCESSORS

=head2 id

  data_type: 'integer'
  extra: {unsigned =&gt; 1}
  is_auto_increment: 1
  is_nullable: 0

=head2 username

  data_type: 'varchar'
  is_nullable: 0
  size: 30

=head2 password

  data_type: 'text'
  is_nullable: 0

=head2 email

  data_type: 'varchar'
  is_nullable: 0
  size: 50

=head2 email_visible

  data_type: 'integer'
  extra: {unsigned =&gt; 1}
  is_nullable: 1

=head2 firstname

  data_type: 'varchar'
  is_nullable: 1
  size: 30

=head2 lastname

  data_type: 'varchar'
  is_nullable: 1
  size: 30

=head2 location

  data_type: 'varchar'
  is_nullable: 1
  size: 100

=head2 about_me

  data_type: 'text'
  is_nullable: 1

=cut

__PACKAGE__-&gt;add_columns(
  "id",
  {
    data_type =&gt; "integer",
    extra =&gt; { unsigned =&gt; 1 },
    is_auto_increment =&gt; 1,
    is_nullable =&gt; 0,
  },
  "username",
  { data_type =&gt; "varchar", is_nullable =&gt; 0, size =&gt; 30 },
  "password",
  { data_type =&gt; "text", is_nullable =&gt; 0 },
  "email",
  { data_type =&gt; "varchar", is_nullable =&gt; 0, size =&gt; 50 },
  "email_visible",
  { data_type =&gt; "integer", extra =&gt; { unsigned =&gt; 1 }, is_nullable =&gt; 1 },
  "firstname",
  { data_type =&gt; "varchar", is_nullable =&gt; 1, size =&gt; 30 },
  "lastname",
  { data_type =&gt; "varchar", is_nullable =&gt; 1, size =&gt; 30 },
  "location",
  { data_type =&gt; "varchar", is_nullable =&gt; 1, size =&gt; 100 },
  "about_me",
  { data_type =&gt; "text", is_nullable =&gt; 1 },
);

=head1 PRIMARY KEY

=over 4

=item * L&lt;/id&gt;

=back

=cut

__PACKAGE__-&gt;set_primary_key("id");

=head1 RELATIONS

=head2 blog_comments

Type: has_many

Related object: L&lt;Yardbird::Schema::Result::BlogComment&gt;

=cut

__PACKAGE__-&gt;has_many(
  "blog_comments",
  "Yardbird::Schema::Result::BlogComment",
  { "foreign.userid" =&gt; "self.id" },
  { cascade_copy =&gt; 0, cascade_delete =&gt; 0 },
);

=head2 blogs

Type: has_many

Related object: L&lt;Yardbird::Schema::Result::Blog&gt;

=cut

__PACKAGE__-&gt;has_many(
  "blogs",
  "Yardbird::Schema::Result::Blog",
  { "foreign.userid" =&gt; "self.id" },
  { cascade_copy =&gt; 0, cascade_delete =&gt; 0 },
);

=head2 user_roles

Type: has_many

Related object: L&lt;Yardbird::Schema::Result::UserRole&gt;

=cut

__PACKAGE__-&gt;has_many(
  "user_roles",
  "Yardbird::Schema::Result::UserRole",
  { "foreign.userid" =&gt; "self.id" },
  { cascade_copy =&gt; 0, cascade_delete =&gt; 0 },
);

=head2 roleids

Type: many_to_many

Composing rels: L&lt;/user_roles&gt; -&gt; roleid

=cut

__PACKAGE__-&gt;many_to_many("roleids", "user_roles", "roleid");


# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-02-25 12:45:10
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:s1r1mjXJqZzRdLk0PGpC9A


# You can replace this text with custom code or comments, and it will be preserved on regeneration

# Have the 'password' column use a SHA-1 hash and 20-byte salt
# with RFC 2307 encoding; Generate the 'check_password" method
__PACKAGE__-&gt;add_columns(
    'password' =&gt; {
        passphrase       =&gt; 'rfc2307',
        passphrase_class =&gt; 'SaltedDigest',
        passphrase_args  =&gt; {
            algorithm   =&gt; 'SHA-1',
            salt_random =&gt; 20.
        },
        passphrase_check_method =&gt; 'check_password',
    },
); 

sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}

__PACKAGE__-&gt;meta-&gt;make_immutable;
1;
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie Experiment 03: Layout</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4310</id>

    <published>2013-02-11T16:12:33Z</published>
    <updated>2013-02-24T20:45:01Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application. In...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html">Notes from a Newbie</a> document the creation and deployment of <a href="http://yardbirdfanclub.org">yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application.</p>

<p>In these notes I continue to explore what it might take to create a blog application, focusing on how to use CSS to layout a page.</p>

<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#100">Create and Use CSS Styles</a>
<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#200">main.css</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#201">header.tt</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#202">footer.tt</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#203">index.tt</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#204">blog.tt</a></li>
</ul></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#101">Review</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html#102">What Next?</a></li>
</ul>
]]>
        <![CDATA[<p><a name='100'></a> </p>

<h2>Create and Use CSS Styles</h2>

<p>I wondered if I could get blog output to look anything like blogs.perl.org, and the answer is yes.</p>

<p>You will need to create:</p>

<ul>
<li>root/static/css/main.css</li>
<li>root/header.tt</li>
<li>root/footer.tt</li>
</ul>

<p>Use them in:</p>

<ul>
<li>root/index.tt</li>
<li>root/blog.tt</li>
</ul>

<p><a name='200'></a></p>

<h3>main.css</h3>

<p>I think there is nothing really fancy here, it is basic CSS as far as I know - but I don't know much.</p>

<p>I created this stylesheet a step at a time, slowly and painstakingly creating and applying styles to get the output I was looking for.</p>

<p>root/static/css/main.css:</p>

<pre><code>#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  margin-left: -1.5em;
  float: left;
}

#main_menu_right li {
  margin-right: 1em;
  float: right;
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}
</code></pre>

<p><a name='201'></a> </p>

<h3>header.tt</h3>

<p>My conception of yardbirdfanclub.org is to have the same menu at the top of each page, and to similarly have a simple footer at the bottom of each page.</p>

<p>root/header.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" &gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
  &lt;title&gt;[% title %]&lt;/title&gt;
  &lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" /&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="wrapper"&gt;
    &lt;div id="main_menu"&gt;
      &lt;ul&gt;
        &lt;div id="main_menu_left"&gt;
          &lt;li&gt;&lt;a href="[% c.uri_for_action('/index') %]"&gt;Home&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;Blog&lt;/li&gt;
          &lt;li&gt;Events&lt;/li&gt;
          &lt;li&gt;Links&lt;/li&gt;
          &lt;li&gt;News&lt;/li&gt;
          &lt;li&gt;Photos&lt;/li&gt;
          &lt;li&gt;Video&lt;/li&gt;
          &lt;li&gt;Members&lt;/li&gt;
        &lt;/div&gt;
        &lt;div id="main_menu_right"&gt;
          &lt;li&gt;Login | Not a member?&lt;/li&gt; 
        &lt;/div&gt;
      &lt;/ul&gt;
    &lt;/div&gt; &lt;!-- main_menu --&gt;
</code></pre>

<p><a name='202'></a> </p>

<h3>footer.tt</h3>

<p>root/footer.tt:</p>

<pre><code>    &lt;div id="footer"&gt;
      &lt;p&gt;Feedback Welcome | Webmaster&lt;span style="float:right"&gt;The YARDBIRD Fan Club&lt;/span&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt; &lt;!-- wrapper --&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p><a name='203'></a> </p>

<h3>index.tt</h3>

<p>This is the template we are using with our index action:</p>

<p>root/index.tt:</p>

<pre><code>&lt;h3&gt;Take A Ride...&lt;/h3&gt;
[% FOREACH blog_entry IN blog_entries %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Edit it to use our styles:</p>

<p>root/index.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="content_without_sidebar"&gt;
    &lt;h3&gt;Take A Ride...&lt;/h3&gt;
    [% FOREACH blog_entry IN blog_entries %]
      &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
    [% END %]
  &lt;/div&gt;
&lt;/div&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='204'></a> </p>

<h3>blog.tt</h3>

<p>This is the template we are using with our blog action:</p>

<p>root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
&lt;p&gt;blog_entry.userid.name: [% blog_entry.userid.name %]&lt;/p&gt;
&lt;p&gt;--------------------------------------&lt;/p&gt;
[% FOREACH user_entry IN all_user_entries %]
  &lt;p&gt;[% user_entry.title %]&lt;/p&gt;
  &lt;p&gt;[% user_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Edit it to use our styles:</p>

<p>root/blog.tt:</p>

<pre><code>[% INCLUDE header.tt title = 'The YARDBIRD Fan Club'; %]
&lt;div id="header"&gt;
  &lt;div id="header_title"&gt;The YARDBIRD Fan Club&lt;/div&gt;
  &lt;div id="header_subtitle"&gt;Celebrating the NFL Rookie Sensation!&lt;/div&gt;
&lt;/div&gt;

&lt;div id="content_wrapper"&gt;
  &lt;div id="sidebar_wrapper"&gt;

    &lt;div class="sidebar_item"&gt;
      &lt;div class="sidebar_item_content"&gt;
        [% FOREACH user_entry IN all_user_entries %]
          &lt;p&gt;[% user_entry.title %]&lt;/p&gt;
          &lt;p&gt;[% user_entry.content %]&lt;/p&gt;
        [% END %]
      &lt;/div&gt; &lt;!-- sidebar_item_content --&gt;
    &lt;/div&gt; &lt;!-- sidebar_item --&gt;
  &lt;/div&gt; &lt;!-- sidebar_wrapper --&gt;

  &lt;div id="content_with_sidebar"&gt;
    &lt;h3&gt;On The Wild Side.&lt;/h3&gt;
    &lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
    &lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
    &lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
    &lt;p&gt;blog_entry.userid.name: [% blog_entry.userid.name %]&lt;/p&gt;
  &lt;/div&gt; &lt;!-- content_with_sidebar --&gt;
&lt;/div&gt; &lt;!-- content_wrapper --&gt;

[% INCLUDE footer.tt %]
</code></pre>

<p><a name='101'></a> </p>

<h2>Review</h2>

<p>Taking what I learned in Catalyst and DBIx::Class docs, I explored how I might create a blog. Being a rather dim-witted Newbie, I wanted to experiment without concern for broader issues one would otherwise take into consideration, such as configuration, controller name/path, file locations, ...</p>

<p>These experiments led to the discovery of details I need to implement a simple blog:</p>

<ul>
<li>Database</li>
<li>Foreign Keys
<ul>
<li>Access other table data</li>
<li>Cascade edit/delete</li>
</ul></li>
<li>Result Class
<ul>
<li>Row method creation and object use</li>
</ul></li>
<li>ResultSet Class
<ul>
<li>ResultSet method creation and object use</li>
</ul></li>
<li>CSS Layout</li>
</ul>

<p><a name='102'></a> </p>

<h2>What Next?</h2>

<p>Now that we have done the most difficult things, we'll start over and create the Yardbird application from scratch, starting first with authentication/authorization.</p>

<p>It will get much easier now.</p>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie Experiment 02: DBIx::Class</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4309</id>

    <published>2013-02-11T16:00:37Z</published>
    <updated>2013-03-24T01:02:15Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application. In...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html">Notes from a Newbie</a> document the creation and deployment of <a href="http://yardbirdfanclub.org">yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application.</p>

<p>In these notes I continue to explore what it might take to create a blog application, focusing on how to use a database.</p>

<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#100">DBIx::Class Vocabulary</a>
<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#200">Result Class</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#201">ResultSet Class</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#202">Row Class</a></li>
</ul></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#101">stash()</a>
<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#203">ResultSet vs. Row </a></li>
</ul></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#102">search()</a>
<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#204">Recreate Database, Model and Schema</a> </li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#205">Create Result Class (Row) Search Methods</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#206">Create ResultSet Class (Query) Search Methods</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#207">AutoCRUD</a></li>
</ul></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html#103">Summary</a></li>
</ul>
]]>
        <![CDATA[<p><a name='100'></a></p>

<h2>DBIx::Class Vocabulary</h2>

<ul>
<li>result class == DBIx::Class::ResultSource == lib/MyApp/Schema/Result == table</li>
<li>query class == DBIx::Class::ResultSet == lib/MyApp/Schema/ResultSet == query (prepare to retrieve zero or more rows)</li>
<li>row class == DBIx::Class::Row == lib/MyApp/Schema/Result (add Row methods to Result classes) == row (retrieve one row)</li>
</ul>

<p><a name='200'></a> </p>

<h3>Result Class</h3>

<ul>
<li>A Result Class Corresponds To A Table</li>
</ul>

<p>In my applications, each Result class will define one table, which defines the columns it has, along with any relationships it has to other tables, and the methods that will be available in the Row objects created using that source.</p>

<p>Result classes are also known as Result sources.</p>

<p>Result classes are defined by calling methods proxied to DBIx::Class::ResultSource.</p>

<p>ResultSources do not need to be directly created, a ResultSource instance is created for each Result class in your Schema, by the proxied methods table and add_columns.</p>

<p>Notice we have one Result class corresponding to our blog table:</p>

<p><img alt="yfc0200.png" src="http://blogs.perl.org/users/j0e/yfc0200.png" width="147" height="81" class="mt-image-none" style="" /></p>

<p><a name='201'></a> </p>

<h3>ResultSet Class</h3>

<ul>
<li>A ResultSet Corresponds To A SQL Query</li>
</ul>

<p>Any time you want to query a table, you'll be creating a DBIx::Class::ResultSet from its ResultSource.</p>

<p>This is an object representing a set of conditions to filter data. It can either be an entire table, or the results of a query. The actual data is not held in the ResultSet, it is only a description of how to fetch the data.</p>

<p>Setting up a ResultSet does not execute the query; retrieving the data does. Search is like "prepare," the query won't execute until you use a method that wants to access the data - such as "next", or "first" and search results are blessed into DBIx::Class::Row objects.</p>

<p>Notice we don't have any ResultSet classes in our application yet, but we will soon create a couple of them.</p>

<p><a name='202'></a> </p>

<h3>Row Class</h3>

<ul>
<li>Row objects contain your actual data. They are returned from ResultSet objects.</li>
</ul>

<p>See:</p>

<ul>
<li>DBIx::Class::Manual::Glossary</li>
<li>DBIx::Class::ResultSet</li>
<li>DBIx::Class::ResultSource </li>
<li>DBIx::Class::Row</li>
</ul>

<p><a name='101'></a> </p>

<h2>stash()</h2>

<p>Though not previously mentioned, there are some subtle, important nuances to code we already created.</p>

<p>Pay attention to what our Root.pm controller actions put into the stash, and how these objects are used in the templates.</p>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;search({})]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h3&gt;Take A Ride...&lt;/h3&gt;
[% FOREACH blog_entry IN blog_entries %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
</code></pre>

<p><a name='203'></a></p>

<h3>ResultSet vs. Row</h3>

<p>It is common to put the results of a search into the stash, and a search returns either a ResultSet or Row objects depending on context, so it is something to pay careful attention to. </p>

<p>It matters to templates what controllers put into the stash, so you must know the difference between ResultSets and Row objects and when and how they are used.</p>

<h4>DBIx::Class::ResultSet</h4>

<p>Method: search</p>

<p>Return Value: $resultset (scalar context) || @row_objs (list context)</p>

<p>The DBIx::Class::ResultSet doc says the ResultSet search method returns a ResultSet in scalar context, else a list of Row objects in list context.</p>

<h4>What Is Our Code Doing?</h4>

<p>In what context, scalar or list, are we searching?</p>

<p>What are my Root.pm controller actions putting into the stash, ResultSet or Row objects?</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;search({})]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>A hash is a list, and this is a hash assignment, so to my way of thinking, search is being evaluated in list context and returns a Row object:</p>

<pre><code>$c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));
</code></pre>

<p>In our index action, even before the hash assignment, we evaluate search inside an anonymous array operator. Thus to my way of thinking search is again evaluated in list context, this time returning a list of Row objects:</p>

<pre><code>$c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;search({})]);
</code></pre>

<p>If I understand this correctly, my index action puts an array of Row objects and my blog action a single Row object into the stash. Compare how their respective templates use the stash, it seems to support my explanation.</p>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;search({})]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h3&gt;Take A Ride...&lt;/h3&gt;
[% FOREACH blog_entry IN blog_entries %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
</code></pre>

<h4>What Was Our Code Doing?</h4>

<p>Recall what our index method was doing before we improved the code:</p>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  my $rs = $c-&gt;model('DB::Blog')-&gt;search({});

  my $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t1} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c1} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id1} = $blog-&gt;id;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t2} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c2} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id2} = $blog-&gt;id;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t3} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c3} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id3} = $blog-&gt;id;

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>The search method was evaluated in scalar context, so a ResultSet was returned from search. The Row method 'next' was then used on the ResultSet to put Row object values into the stash for the template to use.</p>

<p><a name='102'></a> </p>

<h2>search()</h2>

<p>In this section we will experiment with Result and ResultSet search methods to find and use data. We will:</p>

<p>1) Recreate a database with new test data, adding a user table with id, username, firstname, and lastname. Make userid a foreign key in the blog table.</p>

<p>2) Create a user Result class search method to return a name:</p>

<p>Users will be required to create a username when becoming members of yardbirdfanclub.org. Firstname and lastname will be optional, but will be displayed instead of username if either or both are provided. </p>

<ul> 
<li>Returns username if neither firstname or lastname are given. Returns firstname if firstname is given but lastname is not given. Returns lastname if lastname is given but firstname is not given. Returns a concatenated "firstname lastname" if both are given.</li> 
</ul> 

<p>3) Create blog ResultSet search methods to return:</p>

<ul>
<li>A particular blog entry.</li>
<li>All recent blog entries, sorted with most recent first.</li>
<li>All blog entries of a particular user, sorted with most recent first.</li>
</ul>

<p><a name='204'></a> </p>

<h3>Recreate Database, Model and Schema</h3>

<p>Recreate a database with new test data, adding a user table with id, username, firstname, and lastname. Make userid a foreign key in the blog table.</p>

<h4>Create Database</h4>

<p>yardbird01.sql:</p>

<pre><code>--
CREATE TABLE `user` (
  id            INT UNSIGNED NOT NULL AUTO_INCREMENT,
  username      VARCHAR(30) NOT NULL,
  firstname     VARCHAR(30),
  lastname      VARCHAR(30),
  PRIMARY KEY (id)
) ENGINE=InnoDB;
--
CREATE TABLE blog (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  userid  INT UNSIGNED NOT NULL,
  title   TEXT NOT NULL,
  content TEXT NOT NULL,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
--
INSERT INTO `user` (id, username) VALUES (1, 'webmaster');
INSERT INTO `user` (id, username, firstname) VALUES (2, 'sally1', 'Mustang');
INSERT INTO `user` (id, username, lastname) VALUES (3, 'sally2', 'Sally');
INSERT INTO `user` (id, username, firstname, lastname) VALUES (4, 'sally3', 'Mustang', 'Sally');
INSERT INTO `user` (id, username, firstname, lastname) VALUES (5, 'billyt', 'Billy T.', 'Barfoe');
--
INSERT INTO blog (id, userid, title, content) VALUES (1, 1, 'Welcome webmaster',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (2, 2, 'Welcome sally1',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (3, 3, 'Welcome sally2',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (4, 4, 'Welcome sally3',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (5, 5, 'Welcome billyt',
  'Congratulations, you are now a member of yardbirdfanclub.org.');
</code></pre>

<p>Create the database:</p>

<pre><code>&gt; mysql -uusername -ppassword
mysql&gt; create database yardbird01;
mysql&gt; quit;

&gt; mysql -uusername -ppassword yardbird01 &lt; yardbird01.sql

&gt; mysql -uusername -ppassword
mysql&gt; show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| yardbird00         |
| yardbird01         |
+--------------------+

mysql&gt; use yardbird01;
mysql&gt; show tables;
+----------------------+
| Tables_in_yardbird01 |
+----------------------+
| blog                 |
| user                 |
+----------------------+

mysql&gt; describe blog;
+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| userid  | int(10) unsigned | NO   | MUL | NULL    |                |
| title   | text             | NO   |     | NULL    |                |
| content | text             | NO   |     | NULL    |                |
+---------+------------------+------+-----+---------+----------------+

mysql&gt; describe user;
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| username  | varchar(30)      | NO   |     | NULL    |                |
| firstname | varchar(30)      | YES  |     | NULL    |                |
| lastname  | varchar(30)      | YES  |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+

mysql&gt; select * from blog;
+----+--------+-------------------+---------------------------------------------------------------+
| id | userid | title             | content                                                       |
+----+--------+-------------------+---------------------------------------------------------------+
|  1 |      1 | Welcome webmaster | Congratulations, you are now a member of yardbirdfanclub.org. |
|  2 |      2 | Welcome sally1    | Congratulations, you are now a member of yardbirdfanclub.org. |
|  3 |      3 | Welcome sally2    | Congratulations, you are now a member of yardbirdfanclub.org. |
|  4 |      4 | Welcome sally3    | Congratulations, you are now a member of yardbirdfanclub.org. |
|  5 |      5 | Welcome billyt    | Congratulations, you are now a member of yardbirdfanclub.org. |
+----+--------+-------------------+---------------------------------------------------------------+

mysql&gt; select * from user;
+----+-----------+-----------+----------+
| id | username  | firstname | lastname |
+----+-----------+-----------+----------+
|  1 | webmaster | NULL      | NULL     |
|  2 | sally1    | Mustang   | NULL     |
|  3 | sally2    | NULL      | Sally    |
|  4 | sally3    | Mustang   | Sally    |
|  5 | billyt    | Billy T.  | Barfoe   |
+----+-----------+-----------+----------+

mysql&gt; quit;
</code></pre>

<h4>Recreate Model and Schema</h4>

<pre><code>script/yardbird_create.pl model DB DBIC::Schema \
Yardbird::Schema create=static dbi:mysql:yardbird01 username password
</code></pre>

<p>We now have an additional Result class for our new user table, and a new model. We will need to rename or delete DB.pm and rename DB.pm.new to DB.pm:</p>

<p><img alt="yfc0201.png" src="http://blogs.perl.org/users/j0e/yfc0201.png" width="126" height="183" class="mt-image-none" style="" /></p>

<p>Run the application, you should get the same page as you had before creating the new database, this time with new data.</p>

<pre><code>&gt; script/yardbird_server.pl -r
</code></pre>

<p><a name='205'></a> </p>

<h3>Create Result Class (Row) Search Methods</h3>

<p>Create a user Result class search method to return a name:</p>

<p>Users will be required to create a username when becoming members of yardbirdfanclub.org. Firstname and lastname will be optional, but will be displayed instead of username if either or both are provided. </p>

<p>You want any new code or comments you add to Result classes preserved whenever you update your schema. This will automatically occur, but you must add it to the area provided:</p>

<p>Schema/Result/User.pm:</p>

<pre><code># You can replace this text with custom code or comments, and it will be preserved on regeneration.

sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}

__PACKAGE__-&gt;meta-&gt;make_immutable;
1;
</code></pre>

<p>We need to add a line of code to our blog.tt template to put our new User Row method to work:</p>

<pre><code>&lt;p&gt;blog_entry.userid.name: [% blog_entry.userid.name %]&lt;/p&gt;
</code></pre>

<p>Notice that the template is accessing user table data through the blog table's foreign key. This shows us that a foreign key allows a table to access data in a table it references. Since it cascade updates and deletes, once we get around to removing users from the database, any blog rows referencing a user will automatically be deleted when a user is deleted from the user table.</p>

<p>Run the application and you will see user's names output to specification.</p>

<p><a name='206'></a> </p>

<h3>Create ResultSet Class (Query) Search Methods</h3>

<h4>A Particular Blog Entry</h4>

<p>We have already written code to search for a particular blog entry, in our blog action:</p>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Let's take the search code out of the controller and put it into the model where it belongs.</p>

<p>As previously mentioned, we don't have any ResultSet classes in our application yet. Unlike Result classes, Catalyst didn't automatically create them for me but that is no problem, we will easily create them ourselves.</p>

<p>We need to create a ResultSet directory under our Schema class, and create our ResultSet classes there.</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm:</p>

<pre><code>package Yardbird::Schema::ResultSet::Blog;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific {
  my ($self, $bID) = @_;

  return $self-&gt;search({id =&gt; $bID});
} 

1;
</code></pre>

<p>Now we can use our new search method in our controller:</p>

<p>Yardbird/lib/Controller/Root.pm:</p>

<pre><code>sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;specific($id));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Run the application and you should see no difference from how it ran before, i.e. the blog action continues to put the correct Row object into the stash to view.</p>

<pre><code>&gt; script/yardbird_server.pl -r
</code></pre>

<h4>All Recent Blog Entries, Sorted With Most Recent First</h4>

<p>Have you noticed our blog entries are listed in order with the oldest blog entry at the top of the list?</p>

<p><img alt="yfc0202.png" src="http://blogs.perl.org/users/j0e/yfc0202.png" width="375" height="348" class="mt-image-none" style="" /></p>

<p>I want them ordered with the newest blog entry at the top of the list! Furthermore, I only want to see the most recent blog entries.</p>

<p>Create a method to sort and show the most recent blog entries:</p>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm:</p>

<pre><code>sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, {order_by =&gt; {-desc =&gt; ['id']}, rows =&gt; 4});
}
</code></pre>

<p>Since the values of our blog table id column increase as new blog entries are created, we need to sort by descending order to get the newest ones at the top of the list. Since we only have 5 blog entries at this time, I will set the number of recent entries to display to be 4.</p>

<p>Let's use it in our index action, so that we only see the most recent four blog entries sorted with the newest (Billy T. Barfoe's) at the top.</p>

<p>Yardbird/lib/Controller/Root.pm:</p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Now when we run the application we no longer see the webmaster's blog entry, and the entries are ordered with the most recent at the top:</p>

<p><img alt="yfc0203.png" src="http://blogs.perl.org/users/j0e/yfc0203.png" width="375" height="284" class="mt-image-none" style="" /></p>

<h4>All Blog Entries Of A Particular User, Sorted With Most Recent First</h4>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm:</p>

<pre><code>sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
}
</code></pre>

<p>Creating this search method may be easier to understand than what I will show you to use it. We will edit our blog action and corresponding template, our biggest challenge being how to find a user's id in the controller, and we will need a Row object to do that.</p>

<p>Instead of putting our Row object in the stash like we have been:</p>

<p>Yardbird/lib/Controller/Root.pm:</p>

<pre><code>sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;specific($id));

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Let's change the code to give us the same outcome, with a $row object we may then use to identify a user whose entries we will search for:</p>

<pre><code>sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($id)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Now with this $row object, we have access to the id of the user who's blog entries we want to search for.</p>

<pre><code>sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($id)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(all_user_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);

  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>We can show a user's entries in a loop:</p>

<pre><code>[% FOREACH user_entry IN all_user_entries %]
  &lt;p&gt;[% user_entry.title %]&lt;/p&gt;
  &lt;p&gt;[% user_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Do something like this with it:</p>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
&lt;p&gt;blog_entry.userid.name: [% blog_entry.userid.name %]&lt;/p&gt;
&lt;p&gt;--------------------------------------&lt;/p&gt;
[% FOREACH user_entry IN all_user_entries %]
  &lt;p&gt;[% user_entry.title %]&lt;/p&gt;
  &lt;p&gt;[% user_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Unfortunately we only have one blog entry in our database per user, so you will need to add blog entries for a particular user to see if our new search method is working correctly. Fortunately you can do this very easily with AutoCRUD.</p>

<p><a name='207'></a> </p>

<h3>AutoCRUD</h3>

<p>All it takes to use AutoCRUD is two things:</p>

<p>1) Use AutoCRUD in your application class:</p>

<p>Yardbird/lib/Yardbird.pm:</p>

<pre><code>use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple
  AutoCRUD
/;
</code></pre>

<p>2) Give your browser the location to use AutoCRUD:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>

<p><a name='103'></a> </p>

<h2>Summary</h2>

<p>yardbird01.sql:</p>

<pre><code>--
CREATE TABLE `user` (
  id            INT UNSIGNED NOT NULL AUTO_INCREMENT,
  username      VARCHAR(30) NOT NULL,
  firstname     VARCHAR(30),
  lastname      VARCHAR(30),
  PRIMARY KEY (id)
) ENGINE=InnoDB;
--
CREATE TABLE blog (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  userid  INT UNSIGNED NOT NULL,
  title   TEXT NOT NULL,
  content TEXT NOT NULL,
  FOREIGN KEY (userid) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (id)
) ENGINE=InnoDB;
--
INSERT INTO `user` (id, username) VALUES (1, 'webmaster');
INSERT INTO `user` (id, username, firstname) VALUES (2, 'sally1', 'Mustang');
INSERT INTO `user` (id, username, lastname) VALUES (3, 'sally2', 'Sally');
INSERT INTO `user` (id, username, firstname, lastname) VALUES (4, 'sally3', 'Mustang', 'Sally');
INSERT INTO `user` (id, username, firstname, lastname) VALUES (5, 'billyt', 'Billy T.', 'Barfoe');
--
INSERT INTO blog (id, userid, title, content) VALUES (1, 1, 'Welcome webmaster',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (2, 2, 'Welcome sally1',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (3, 3, 'Welcome sally2',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (4, 4, 'Welcome sally3',
  'Congratulations, you are now a member of yardbirdfanclub.org.'); 
--
INSERT INTO blog (id, userid, title, content) VALUES (5, 5, 'Welcome billyt',
  'Congratulations, you are now a member of yardbirdfanclub.org.');
</code></pre>

<p>Yardbird/lib/Controller/Root.pm:</p>

<pre><code>package Yardbird::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;most_recent]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  my $row = $c-&gt;model('DB::Blog')-&gt;specific($id)-&gt;first;
  $c-&gt;stash(blog_entry =&gt; $row);
  $c-&gt;stash(all_user_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;all_user($row-&gt;userid-&gt;id)]);

  $c-&gt;detach($c-&gt;view("TT"));
} 

sub default :Path {
    my ( $self, $c ) = @_;
    $c-&gt;response-&gt;body( 'Page not found' );
    $c-&gt;response-&gt;status(404);
}

sub end : ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h3&gt;Take A Ride...&lt;/h3&gt;
[% FOREACH blog_entry IN blog_entries %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
&lt;p&gt;blog_entry.userid.name: [% blog_entry.userid.name %]&lt;/p&gt;
&lt;p&gt;--------------------------------------&lt;/p&gt;
[% FOREACH user_entry IN all_user_entries %]
  &lt;p&gt;[% user_entry.title %]&lt;/p&gt;
  &lt;p&gt;[% user_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Yardbird/lib/Yardbird/Schema/Result/User.pm:</p>

<pre><code>sub name {
  my ($self) = @_;
  my $bestname;

  if ($self-&gt;firstname) {
    $bestname = $self-&gt;firstname;
    if ($self-&gt;lastname) {
      $bestname .= ' ' . $self-&gt;lastname;
    }
  }
  elsif ($self-&gt;lastname) {
    $bestname = $self-&gt;lastname;
  }
  else {
    $bestname = $self-&gt;username;
  }
  return $bestname;
}
</code></pre>

<p>Yardbird/lib/Yardbird/Schema/ResultSet/Blog.pm:</p>

<pre><code>package Yardbird::Schema::ResultSet::Blog;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub specific {
  my ($self, $bID) = @_;

  return $self-&gt;search({id =&gt; $bID});
} 

sub most_recent {
  my ($self) = @_;

  return $self-&gt;search({}, {order_by =&gt; {-desc =&gt; ['id']}, rows =&gt; 4});
} 

sub all_user {
  my ($self, $uID) = @_;

  return $self-&gt;search({userid =&gt; $uID}, {order_by =&gt; {-desc =&gt; ['id']}});
} 

1;
</code></pre>

<p>1) Use AutoCRUD in your application class:</p>

<p>Yardbird/lib/Yardbird.pm:</p>

<pre><code>use Catalyst qw/
  -Debug
  ConfigLoader
  Static::Simple
  AutoCRUD
/;
</code></pre>

<p>2) Give your browser the location to use AutoCRUD:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie Experiment 01: Simple Dynamic Content and Links</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4307</id>

    <published>2013-02-11T12:49:08Z</published>
    <updated>2013-02-11T17:36:25Z</updated>

    <summary>Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application. In...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<p><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html">Notes from a Newbie</a> document the creation and deployment of <a href="http://yardbirdfanclub.org">yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a Perl Catalyst application.</p>

<p>In these notes I begin to explore what it might take to create a blog application.</p>

<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#200">What Can I Do With A Database?</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#201">What Will It Take To Create A Blog With Catalyst And MySQL?</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#202">Simple Dynamic Content and Links</a>
<ul>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#300">Create MySQL Database</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#301">Create Catalyst Application</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#400">Use Database in Catalyst Application</a></li>
<li><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html#401">Make Content and Links Dynamic</a></li>
</ul></li>
</ul>
]]>
        <![CDATA[<p><a name='200'></a></p>

<h2>What Can I Do With A Database?</h2>

<p>Using what I learned in Catalyst::Tutorial and other perldocs, I want to learn to design and use a simple database in a Catalyst application of my own design, and deploy it on the Internet.</p>

<p><a name='201'></a></p>

<h2>What Will It Take To Create A Blog With Catalyst And MySQL?</h2>

<p>Wondering but not really caring if there are better ways to create blogs with Perl Catalyst, I decide to explore what it might take to create a database and use it to blog. </p>

<p>I ask myself:</p>

<ul>
<li>What do you want your pages to look like?</li>
<li>What database details will you need to use and understand?</li>
</ul>

<p>I worked through Catalyst::Manual::Tutorial.</p>

<p>I farted around with DBIx::Class::Manual but found these the most useful, they explain how to use a database specifically with Catalyst:</p>

<ul>
<li>Catalyst::Manual::Tutorial::03_MoreCatalystBasics</li>
<li>Catalyst::Manual::Tutorial::04_BasicCRUD</li>
<li>Catalyst::Manual::Tutorial::05_Authentication</li>
<li>Catalyst::Manual::Tutorial::06_Authorization</li>
<li>Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormHandler</li>
</ul>

<p>These were especially helpful too:</p>

<ul>
<li>DBIx::Class::Row</li>
<li>DBIx::Class::ResultSource</li>
<li>DBIx::Class::ResultSet</li>
<li>DBIx::Class::Relationship</li>
</ul>

<ul>
<li>HTML::FormHandler::Manual::Tutorial</li>
<li>HTML::FormHandler::Manual::Catalyst</li>
</ul>

<p>Viewing Perl documentation with a web browser works pretty good, I use Perldoc::Server.</p>

<p>I know very little HTML or CSS, just a little from the Internet and a CSS book I worked through. Same goes for Perl, I studied a couple Perl books and am strictly an entry-level Perl Catalyst Newbie.</p>

<p>With meager skills I set out to create and deploy a meager blog with Perl Catalyst and MySQL, not very original but it's the best I came up with.</p>

<h2>blogs.perl.org</h2>

<p>I've not used or seen many blogs, but I like the design of blogs.perl.org. I wondered if I might be able to create one similarly?</p>

<p><a name='202'></a></p>

<h2>Simple Dynamic Content and Links</h2>

<p>Simple-minded as I am, I ask myself how the blogs.perl.org blog titles are made into links?</p>

<p><a name='300'></a></p>

<h3>Create MySQL Database</h3>

<p>What might a blog database table contain?</p>

<ul>
<li>id (primary key)</li>
<li>userid (foreign key)</li>
<li>title</li>
<li>content</li>
</ul>

<p>You may be wondering about the userid column, what am I using it for?</p>

<p>After doing some experiments, I will create my Yardbird Catalyst application starting with authentication and authorization as explained in:</p>

<ul>
<li>Catalyst::Manual::Tutorial::05_Authentication</li>
<li>Catalyst::Manual::Tutorial::06_Authorization</li>
</ul>

<p>Anticipating not only user and blog tables but a blog_comment table as well, the userid foreign key will give the Yardbird application access to a blog author's name and other personal info via the blog table. The userid foreign key also enables the ability to cascade edit and delete, greatly decreasing coding effort while increasing reliability.</p>

<p>What happens when a member of The YARDBIRD Fan Club no-longer wants to be a member? Will I want to delete the user but keep the user's blog entries and comments? No way, be done with them, but how? This is where the cascade edit/delete feature comes in. When a user is removed I also want that user's blog entries and comments to be deleted too.</p>

<p>What would you rather do, search for and delete each blog entry and comment individually, or have the application delete them automatically for you?</p>

<p>The blog table's foreign key userid provides the means for blog rows to automatically be (cascade) deleted when corresponding user's are deleted.</p>

<p>The blog table's foreign key userid also allows the application to access user table information, such as a blog author's name, through the blog table. This is very important!</p>

<p>I want to keep this example simple so I won't declare userid to be a foreign key yet, but I will do so later.</p>

<h4>yardbird00.sql</h4>

<pre><code>CREATE TABLE blog (
  id      INT UNSIGNED NOT NULL AUTO_INCREMENT,
  userid  INT UNSIGNED NOT NULL,
  title   TEXT NOT NULL,
  content TEXT NOT NULL,
  PRIMARY KEY (id)
);
INSERT INTO blog (id, userid, title, content) VALUES (1, 1, 'title id: 1 userid: 1', 'content id: 1 userid: 1'); 
INSERT INTO blog (id, userid, title, content) VALUES (2, 1, 'title id: 2 userid: 1', 'content id: 2 userid: 1'); 
INSERT INTO blog (id, userid, title, content) VALUES (3, 1, 'title id: 3 userid: 1', 'content id: 3 userid: 1');
</code></pre>

<h4>mysql> create database yardbird00;</h4>

<pre><code>&gt; mysql -uusername -ppassword
mysql&gt; create database yardbird00;
mysql&gt; quit;
&gt; mysql -uusername -ppassword yardbird00 &lt; yardbird00.sql
&gt; mysql -uusername -ppassword 
mysql&gt; show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| yardbird00         |
+--------------------+
mysql&gt; use yardbird00;
mysql&gt; show tables;
+----------------------+
| Tables_in_yardbird00 |
+----------------------+
| blog                 |
+----------------------+
mysql&gt; describe blog;
+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| userid  | int(10) unsigned | NO   |     | NULL    |                |
| title   | text             | NO   |     | NULL    |                |
| content | text             | NO   |     | NULL    |                |
+---------+------------------+------+-----+---------+----------------+
mysql&gt; select * from blog;
+----+--------+-----------------------+-------------------------+
| id | userid | title                 | content                 |
+----+--------+-----------------------+-------------------------+
|  1 |      1 | title id: 1 userid: 1 | content id: 1 userid: 1 |
|  2 |      1 | title id: 2 userid: 1 | content id: 2 userid: 1 |
|  3 |      1 | title id: 3 userid: 1 | content id: 3 userid: 1 |
+----+--------+-----------------------+-------------------------+
mysql&gt; quit;
</code></pre>

<p><a name='301'></a></p>

<h3>Create Catalyst Application</h3>

<pre><code>&gt; catalyst.pl Yardbird

Yardbird&gt; perl Makefile.PL

Yardbird&gt; script/yardbird_create.pl view TT TT

Yardbird&gt; script/yardbird_create.pl model DB DBIC::Schema \
Yardbird&gt; Yardbird::Schema create=static dbi:mysql:yardbird00 username password
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>hello world
</code></pre>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Run the application:</p>

<pre><code>Yardbird&gt; script/yardbird_server.pl -r
</code></pre>

<p>The browser gives:</p>

<p><img alt="yfc01a.png" src="http://blogs.perl.org/users/j0e/yardbird/yfc01a.png" width="72" height="21" class="mt-image-none" style="" /></p>

<p><a name='400'></a></p>

<h4>Use Database in Catalyst Application</h4>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  my $rs = $c-&gt;model('DB::Blog')-&gt;search({});

  my $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t1} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c1} = $blog-&gt;content;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t2} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c2} = $blog-&gt;content;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t3} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c3} = $blog-&gt;content;

  $c-&gt;detach($c-&gt;view("TT")); 
}
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h1&gt;Take A Ride&lt;/h1&gt;
&lt;p&gt;[% t1 %]&lt;/p&gt;
&lt;p&gt;[% c1 %]&lt;/p&gt;

&lt;p&gt;[% t2 %]&lt;/p&gt;
&lt;p&gt;[% c2 %]&lt;/p&gt;

&lt;p&gt;[% t3 %]&lt;/p&gt;
&lt;p&gt;[% c3 %]&lt;/p&gt;
</code></pre>

<p>The browser gives:</p>

<p><img alt="yfc01b.png" src="http://blogs.perl.org/users/j0e/yfc01b.png" width="160" height="223" class="mt-image-none" style="" /></p>

<p><a name='401'></a></p>

<h4>Make Content and Links Dynamic</h4>

<h5>Links</h5>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  my $rs = $c-&gt;model('DB::Blog')-&gt;search({});

  my $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t1} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c1} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id1} = $blog-&gt;id;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t2} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c2} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id2} = $blog-&gt;id;

  $blog = $rs-&gt;next;
  $c-&gt;stash-&gt;{t3} = $blog-&gt;title;
  $c-&gt;stash-&gt;{c3} = $blog-&gt;content;
  $c-&gt;stash-&gt;{id3} = $blog-&gt;id;

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash-&gt;{id} = $id;
  $c-&gt;detach($c-&gt;view("TT"));
}
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h1&gt;Take A Ride...&lt;/h1&gt;
&lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', id1) %]"&gt;[% t1 %]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[% c1 %]&lt;/p&gt;

&lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', id2) %]"&gt;[% t2 %]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[% c2 %]&lt;/p&gt;

&lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', id3) %]"&gt;[% t3 %]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[% c3 %]&lt;/p&gt;
</code></pre>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h1&gt;...On The Wild Side.&lt;/h1&gt;
&lt;p&gt;[% id %]&lt;/p&gt;
</code></pre>

<p>The browser gives:</p>

<p><img alt="yfc01c.png" src="http://blogs.perl.org/users/j0e/yfc01c.png" width="165" height="202" class="mt-image-none" style="" /></p>

<p>Selecting the first title gives:</p>

<p><img alt="yfc01c1.png" src="http://blogs.perl.org/users/j0e/yfc01c1.png" width="200" height="50" class="mt-image-none" style="" /></p>

<h5>Improve Code</h5>

<p>Yardbird/lib/Yardbird/Controller/Root.pm: </p>

<pre><code>package Yardbird::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  $c-&gt;stash(blog_entries =&gt; [$c-&gt;model('DB::Blog')-&gt;search({})]);

  $c-&gt;detach($c-&gt;view("TT")); 
}

sub blog :Path('blog') :Args(1) {
  my ( $self, $c, $id ) = @_;

  $c-&gt;stash(blog_entry =&gt; $c-&gt;model('DB::Blog')-&gt;search({id =&gt; $id}));

  $c-&gt;detach($c-&gt;view("TT"));
} 

sub default :Path {
    my ( $self, $c ) = @_;
    $c-&gt;response-&gt;body( 'Page not found' );
    $c-&gt;response-&gt;status(404);
}

sub end : ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Yardbird/root/index.tt:</p>

<pre><code>&lt;h3&gt;Take A Ride...&lt;/h3&gt;
[% FOREACH blog_entry IN blog_entries %]
  &lt;p&gt;&lt;a href="[% c.uri_for_action('/blog', blog_entry.id) %]"&gt;[% blog_entry.title %]&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;[% blog_entry.content %]&lt;/p&gt;
[% END %]
</code></pre>

<p>Yardbird/root/blog.tt:</p>

<pre><code>&lt;h3&gt;On The Wild Side.&lt;/h3&gt;
&lt;p&gt;blog_entry.id: [% blog_entry.id %]&lt;/p&gt;
&lt;p&gt;blog_entry.title: [% blog_entry.title %]&lt;/p&gt;
&lt;p&gt;blog_entry.content: [% blog_entry.content %]&lt;/p&gt;
</code></pre>

<p>The browser gives:</p>

<p><img alt="yfc01d.png" src="http://blogs.perl.org/users/j0e/yfc01d.png" width="130" height="215" class="mt-image-none" style="" /></p>

<p>Selecting the third title gives:</p>

<p><img alt="yfc01d3.png" src="http://blogs.perl.org/users/j0e/yfc01d3.png" width="230" height="113" class="mt-image-none" style="" /></p>

<p>We have finished the difficult part, it will get much easier now.</p>
]]>
    </content>
</entry>

<entry>
    <title>Notes from a Newbie</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html" />
    <id>tag:blogs.perl.org,2013:/users/j0e//1287.4306</id>

    <published>2013-02-11T12:35:48Z</published>
    <updated>2013-05-13T23:32:13Z</updated>

    <summary> Experiment 01: Simple Dynamic Content and Links Experiment 02: DBIx::Class Experiment 03: Layout Notes from a Newbie 10: Authentication/Authorization Notes from a Newbie 11: View Blogs Notes from a Newbie 12: Create and View Comments Notes from a Newbie...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystnewbie" label="Perl Catalyst Newbie" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-01-simple-dynamic-content-and-links.html' target='_blank'>Experiment 01: Simple Dynamic Content and Links</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-02-dbixclass.html' target="_blank">Experiment 02: DBIx::Class</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie-experiment-03-layout.html' target="_blank">Experiment 03: Layout</a></li>
</ul>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-10-authenticationauthorization.html' target='_blank'>Notes from a Newbie 10: Authentication/Authorization</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-11-view-blogs.html' target='_blank'>Notes from a Newbie 11: View Blogs</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/03/notes-from-a-newbie-12-create-and-view-comments.html' target='_blank'>Notes from a Newbie 12: Create and View Comments</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-13-create-edit-and-delete-blog-entries.html' target='_blank'>Notes from a Newbie 13: Create, Edit and Delete Blog Entries</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-14-edit-and-delete-comments.html' target='_blank'>Notes from a Newbie 14: Edit and Delete Comments</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/04/notes-from-a-newbie-15-edit-delete-and-view-members.html' target='_blank'>Notes from a Newbie 15: Edit, Delete and View Members</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2013/05/notes-from-a-newbie-16-deploy.html' target='_blank'>Notes from a Newbie 16: Deploy</a></li>
</ul>

<p><a href="http://blogs.perl.org/users/j0e/2013/02/notes-from-a-newbie.html">Notes from a Newbie</a> document the creation and deployment of <a href='http://yardbirdfanclub.org' target="_blank">yardbirdfanclub.org</a> with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.</p>
]]>
        <![CDATA[<p>Previous blog entries include:</p>

<ul>
<li><a href='http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html' target="_blank">Using jqGrid with Catalyst: Tutorial with Examples for Newbies</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2012/09/using-catalystcontrollerrest-with-jqgrid-tutorial-with-examples-for-newbies.html' target="_blank">Using Catalyst::Controller::REST with jqGrid: Tutorial with Examples for Newbies</a></li>
<li><a href='http://blogs.perl.org/users/j0e/2012/09/using-catalystpluginautocrud-tutorial-with-examples-for-newbies.html' target="_blank">Using Catalyst::Plugin::AutoCRUD: Tutorial with Examples for Newbies</a></li>
</ul>
]]>
    </content>
</entry>

<entry>
    <title>Using Catalyst::Plugin::AutoCRUD: Tutorial with Examples for Newbies </title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2012/09/using-catalystpluginautocrud-tutorial-with-examples-for-newbies.html" />
    <id>tag:blogs.perl.org,2012:/users/j0e//1287.3837</id>

    <published>2012-09-16T12:16:53Z</published>
    <updated>2012-09-16T23:18:23Z</updated>

    <summary> Introduction Don&apos;t Blink! As Simple As That! What Next?...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="catalyst" label="Catalyst" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="catalystpluginautocrud" label="Catalyst::Plugin::AutoCRUD" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<ul>
<li>Introduction</li>
<li>Don't Blink!</li>
<li>As Simple As That!</li>
<li>What Next?</li>
</ul>
]]>
        <![CDATA[<h2>Introduction</h2>

<p>This is the third in a series of Catalyst tutorials for Newbies, the first two were:</p>

<ul>
<li><p><a href="http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html">Using jqGrid with Catalyst: Tutorial with Examples for Newbies</a>  </p></li>
<li><p><a href="http://blogs.perl.org/users/j0e/2012/09/using-catalystcontrollerrest-with-jqgrid-tutorial-with-examples-for-newbies.html">Using Catalyst::Controller::REST with jqGrid: Tutorial with Examples for Newbies</a></p></li>
</ul>

<p>In the first two tutorials we use jqGrid to display and edit a MySQL or SQLite table using 1) Catalyst::View::JSON, 2) Catalyst::Controller::REST and Catalyst::View::JSON, and 3)  Catalyst::Controller::REST without the JSON view. We use Catalyst::TraitFor::Controller::jQuery::jqGrid to make our work easier.</p>

<p>In this third tutorial we will use Catalyst::Plugin::AutoCRUD to create a grid to display our MySQL or SQLite table. There really is nothing to it, it is so quick and easy you'll miss it if you blink your eyes!</p>

<h2>Don't Blink!</h2>

<p>From the Catalyst::Plugin::AutoCRUD docs:</p>

<p>You have a database, and wish to have a basic web interface supporting Create, Retrieve, Update, Delete and Search, with little effort. This module is able to create such interfaces on the fly. </p>

<ul>
<li>See the demo at: <a href="http://demo.autocrud.pl">http://demo.autocrud.pl</a></li>
</ul>

<p>If you already have a Catalyst app with DBIx::Class models configured:</p>

<pre><code>use Catalyst qw(AutoCRUD); # &lt;-- add the plugin name here in MyApp.pm
</code></pre>

<p>Now load your app in a web browser, but add /autocrud to the URL path.</p>

<h2>As Simple As That!</h2>

<p>Configure Catalyst::Plugin::AutoCRUD in the application class of your DBIx::Class configured application and point your browser to /autocrud, as simple as that:</p>

<p>MyFirstGrid/lib/MyFirstGrid.pm:</p>

<pre><code>package MyFirstGrid;
use Moose;
use namespace::autoclean;

use Catalyst::Runtime 5.80;

use Catalyst qw/
    -Debug
    ConfigLoader
    Static::Simple
    AutoCRUD 
/;

extends 'Catalyst';

our $VERSION = '0.01';

__PACKAGE__-&gt;config(
    name =&gt; 'MyFirstGrid',
    # Disable deprecated behavior needed by old applications
    disable_component_resolution_regex_fallback =&gt; 1,
    enable_catalyst_header =&gt; 1, # Send X-Catalyst header
);

# Specify which stash keys are exposed as a JSON response. 
__PACKAGE__-&gt;config({
  'View::JSON' =&gt; {
    expose_stash =&gt; qw(json_data)
  }
});

# Start the application
__PACKAGE__-&gt;setup();

1;
</code></pre>

<p>Run it:</p>

<pre><code>[hack@hackpad MyFirstGrid]$ ./script/myfirstgrid_server.pl -d -r -k
</code></pre>

<p>Go here:</p>

<pre><code>http://0.0.0.0:3000/autocrud
</code></pre>

<p>There is no need for me to tell you more about Catalyst::Plugin::AutoCRUD because it has already been said. Here is a good place to start:</p>

<ul>
<li><a href="http://marcus.nordaaker.com/catalystpluginautocrud">http://marcus.nordaaker.com/catalystpluginautocrud</a></li>
</ul>

<p>AutoCRUD supports multiple DBIC Schemas, and it will automatically provide you with a list to let you pick which one to work with. After that, you choose a Result class, and you have access to an extensive AJAX-enhanched database admin tool. You can search and browse data, as well as edit it and add new rows easily. AutoCRUD also understands your DBIC relationships, so you can easily see data related to the current rows.</p>

<p>Like it’s predecessors, I do not recommend trying to make this tool into a generic 'allweb'-application. However, if you use it for what it is, you can save countless development hours making trivial admin tools. Since it uses your DBIC Schema, you’ll also get the advantage of keeping your business logic in the data model. Things like DBIC timestamps will Just Work.</p>

<p>A more in-depth yet easy to follow discussion:</p>

<ul>
<li><a href="http://www.catalystframework.org/calendar/2011/11">http://www.catalystframework.org/calendar/2011/11</a> </li>
</ul>

<p>Some helper scripts which accompany Catalyst modules will generate CRUD code, subroutine stubs, package templates, and so on. You can use these as a starting point from which to grow your application, and the term for this is scaffolding.</p>

<p>In the Python world there's a plugin for their Django web framework which is a bit like scaffold helper scripts on steroids. It generates a fully working web interface for manipulating back-end data.</p>

<p>Catalyst's AutoCRUD plugin does something similar. After installing the plugin you have a new URL path at /autocrud from which you can access and edit any data accessible through the app Models. However unlike Django's plugin, this is all done on the fly, there are no scaffolding files written to disk. To give you an idea of what's generated, head over to the demo site at <a href="http://demo.autocrud.pl">http://demo.autocrud.pl</a>.</p>

<p>That doesn't mean you can't control what AutoCRUD produces. This article will look at a few ways to tailor the appearance of the Catalyst::Plugin::AutoCRUD products. </p>

<p>We started with a simple idea - producing a simple user interface allowing Create, Search, Update and Delete on your back-end data. You can start with scaffolding files produced by helper scripts, or with a plugin like AutoCRUD very quickly have an app which does the same, all on the fly.</p>

<p>Which way you go is your choice. There's a trade-off between effort, and flexibility and power. AutoCRUD requires little effort and delivers a lot, and is even configurable to some degree. But maybe there comes a point when your app grows and you need more. </p>

<p>Documentation:</p>

<ul>
<li><a href="https://metacpan.org/module/Catalyst::Plugin::AutoCRUD">https://metacpan.org/module/Catalyst::Plugin::AutoCRUD</a> </li>
</ul>

<p>This module contains an application which will automatically construct a web interface for a database on the fly. The web interface supports Create, Retrieve, Update, Delete and Search operations.</p>

<p>The interface is not written to static files on your system, and uses AJAX to act upon the database without reloading your web page (much like other Web 2.0 applications, for example Google Mail).</p>

<p>Almost all the information required by the plugin is retrieved from the DBIx::Class ORM frontend to your database, which it is expected that you have already set up. This means that any change in database schema ought to be reflected immediately in the web interface after a page refresh. </p>

<p>Scenario 1: Plugin to an existing Catalyst App</p>

<p>This mode is for when you have written your Catalyst application, but the Views are catering for the users and as an admin you'd like a more direct, secondary web interface to the database. </p>

<p>Scenario 2: Frontend for an existing DBIx::Class::Schema based class</p>

<p>In this mode, Catalyst::Plugin::AutoCRUD is running standalone, in a sense as the Catalyst application itself. </p>

<p>Scenario 3: Lazy loading a DBIx::Class schema</p>

<p>If you're in such a hurry that you can't create the DBIx::Class schema, then Catalyst::Plugin::AutoCRUD is able to do this on the fly, but it will slow the application's startup just a little. </p>

<p>SEE ALSO:</p>

<p>CatalystX::CRUD and CatalystX::CRUD::YUI are two distributions which allow you to create something similar but with full customization, and the ability to add more features. So, you trade effort for flexibility and power.</p>

<h2>What Next?</h2>

<p>Suggestions?</p>
]]>
    </content>
</entry>

<entry>
    <title>Using Catalyst::Controller::REST with jqGrid: Tutorial with Examples for Newbies</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2012/09/using-catalystcontrollerrest-with-jqgrid-tutorial-with-examples-for-newbies.html" />
    <id>tag:blogs.perl.org,2012:/users/j0e//1287.3831</id>

    <published>2012-09-15T04:41:02Z</published>
    <updated>2012-09-17T15:03:00Z</updated>

    <summary> Introduction Get Started! Using Catalyst::Controller::REST with Catalyst::View::JSON Using Catalyst::Controller::REST without Catalyst::View::JSON MySQL to SQLite What&apos;s Next?...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="catalystcatalystcontrollerrestjqgrid" label="Catalyst Catalyst::Controller::REST jqGrid" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<ul>
<li>Introduction</li>
<li>Get Started!</li>
<li>Using Catalyst::Controller::REST with Catalyst::View::JSON</li>
<li>Using Catalyst::Controller::REST without Catalyst::View::JSON</li>
<li>MySQL to SQLite</li>
<li>What's Next?</li>
</ul>
]]>
        <![CDATA[<h2>Introduction</h2>

<p>This is the second in a series of tutorials and examples for Catalyst Newbies. Myself being "over the hill" I naturally feel a kinship with Newbies, and am not ashamed to admit self-identification as a strange old-fashioned geezer-newbie kind of amalgamation. I mean, anybody who would write code in Assembly Language! We'll just stuff those old registers full of stuff, invoke an interrupt or two and be on our way!</p>

<p>In my first tutorial <a href="http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html">http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html</a> I created a Catalyst application using MySQL, jqGrid, Catalyst::View::JSON and Catalyst::TraitFor::Controller::jQuery::jqGrid.</p>

<p>This second tutorial starts where the final version of the first one left off, and adds a REST controller that uses Catalyst::View::JSON with Catalyst::Controller::REST. After we get the REST controller to work with the JSON view, we will then make it work without it. The purpose of this tutorial is to show you two more ways to communicate with jqGrid: Using a REST controller with and without a JSON view.</p>

<p>Unbeknownst to you—diligent fellow-newbies, I moved from using MySQL to SQLite in these tutorials. I will show you how easily that was done.</p>

<h2>Get Started!</h2>

<p>We will continue with the final version from the first tutorial and add a controller to use REST. You may want to copy the MyFirstGrid directory from the final version of the first tutorial, to a new location and use that version for this tutorial.</p>

<p>MyFirstGrid.pm:</p>

<pre><code>package MyFirstGrid;
use Moose;
use namespace::autoclean;

use Catalyst::Runtime 5.80;

use Catalyst qw/
    -Debug
    ConfigLoader
    Static::Simple
/;

extends 'Catalyst';

our $VERSION = '0.01';

__PACKAGE__-&gt;config(
    name =&gt; 'MyFirstGrid',
    # Disable deprecated behavior needed by old applications
    disable_component_resolution_regex_fallback =&gt; 1,
    enable_catalyst_header =&gt; 1, # Send X-Catalyst header
);

# Specify which stash keys are exposed as a JSON response. 
__PACKAGE__-&gt;config({
  'View::JSON' =&gt; {
    expose_stash =&gt; qw(json_data)
  }
});

# Start the application
__PACKAGE__-&gt;setup();

1;
</code></pre>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
    url:"[% c.uri_for("getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel :[ 
      {name:'inv_id', index:'inv_id', width:55, editable:false, key:true}, 
      {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
      {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
    ], 
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
    editurl:"[% c.uri_for("postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Controller/Root.pm:</p>

<pre><code>package MyFirstGrid::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ($self, $c) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub getdata :Local {
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
}

sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

sub default :Path {
  my ($self, $c) = @_;

  $c-&gt;response-&gt;body('Page not found');
  $c-&gt;response-&gt;status(404);
}

# Attempt to render a view, if needed.

sub end :ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<h2>Using Catalyst::Controller::REST with Catalyst::View::JSON</h2>

<p>Use the Catalyst helper to create a new controller:</p>

<pre><code>$ script/myfirstgrid_create.pl controller Rest
</code></pre>

<p>Controller/Rest.pm:</p>

<pre><code>package MyFirstGrid::Controller::Rest;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller';}

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Rest in Rest.');
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>We'll extend Catalyst::Controller::REST with Catalyst::TraitFor::Controller::jQuery::jqGrid:</p>

<p>Controller/Rest.pm:</p>

<pre><code>package MyFirstGrid::Controller::Rest;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Rest in Rest.');
}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Go back and refresh your memory about Catalyst::TraitFor::Controller::jQuery::jqGrid if you don't remember how it can help you:</p>

<p><a href="http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html">http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html</a></p>

<p>We will copy getdata and postrow from the Root controller and use them here in the REST controller. Then we will update url and editurl in our template to use our new REST controller.</p>

<p>Controller/Rest.pm:</p>

<pre><code>package MyFirstGrid::Controller::Rest;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Rest in Rest.');
}

sub getdata :Local :ActionClass('REST') {}

sub getdata_GET { 
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
}

sub postrow :Local :ActionClass('REST') {}

sub postrow_POST {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Update our template to use the REST controller:</p>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
    url:"[% c.uri_for("rest/getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel :[ 
      {name:'inv_id', index:'inv_id', width:55, editable:false, key:true}, 
      {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
      {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
    ], 
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
    editurl:"[% c.uri_for("rest/postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Run it:</p>

<pre><code>$ script/myfirstgrid_server.pl -d -r -k
</code></pre>

<p>Open your browser:</p>

<pre><code>http://0.0.0.0:3000
</code></pre>

<p>If your results are the same as mine, you will get an empty grid with no data.</p>

<p>Ouch, what went wrong?</p>

<p>This is where my skill-level as a Newbie is confirmed: I know how to fix this, but I don't clearly understand why. For some reason the last line of getdata_GET is causing our problem:</p>

<pre><code>$c-&gt;stash-&gt;{current_view} = 'JSON';
</code></pre>

<p>The Catalyst console gives:</p>

<pre><code>[debug] "GET" request for "rest/getdata" from "127.0.0.1"
[debug] Query Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| _search                             | false                                |
| nd                                  | 1347648333373                        |
| page                                | 1                                    |
| rows                                | 10                                   |
| sidx                                | inv_id                               |
| sord                                | desc                                 |
'-------------------------------------+--------------------------------------'
[debug] Serializing with Catalyst::Action::Serialize::JSON
[debug] Response Code: 400; Content-Type: text/plain; Content-Length: 182
[info] Request took 0.102985s (9.710/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /rest/begin                                                | 0.002102s |
| /rest/getdata                                              | 0.000257s |
| /getdata_GET                                               | 0.069467s |
| /rest/end                                                  | 0.020478s |
'------------------------------------------------------------+-----------'
</code></pre>

<p>For some reason we are no longer rendering our JSON view.</p>

<p>Changing the last line of getdata_GET to explicitly render the view fixes it:</p>

<pre><code>$c-&gt;detach( $c-&gt;view("JSON") );
</code></pre>

<p>Run it and we get data in the grid, and a little different feedback from the console:</p>

<pre><code>[debug] "GET" request for "rest/getdata" from "127.0.0.1"
[debug] Query Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| _search                             | false                                |
| nd                                  | 1347649047150                        |
| page                                | 1                                    |
| rows                                | 10                                   |
| sidx                                | inv_id                               |
| sord                                | desc                                 |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 200; Content-Type: application/json; charset=utf-8; Content-Length: 644
[info] Request took 0.094290s (10.606/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /rest/begin                                                | 0.000499s |
| /rest/getdata                                              | 0.000187s |
| /getdata_GET                                               | 0.087410s |
|  -&gt; MyFirstGrid::View::JSON-&gt;process                       | 0.001557s |
| /rest/end                                                  | 0.000208s |
'------------------------------------------------------------+-----------'
</code></pre>

<p>It seems for some reason our JSON view had not been getting rendered. If you know why, please post a comment with an explanation.</p>

<p>Here is the complete REST controller. It should work correctly:</p>

<p>Controller/Rest.pm:</p>

<pre><code>package MyFirstGrid::Controller::Rest;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Rest in Rest.');
}

sub getdata :Local :ActionClass('REST') {}

sub getdata_GET { 
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
#  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
  $c-&gt;detach( $c-&gt;view("JSON") ); 
}

sub postrow :Local :ActionClass('REST') {}

sub postrow_POST {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<h2>Using Catalyst::Controller::REST without Catalyst::View::JSON</h2>

<p>We are now able to use a JSON view with our REST controller. Next we will create a new REST controller, and let it serialize responses itself rather than with a JSON view.</p>

<p>Save Controller/Rest.pm as Controller/Restnv.pm and change class names accordingly:</p>

<p>Controller/Restnv.pm:</p>

<pre><code>package MyFirstGrid::Controller::Restnv;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Restnv in Rest.');
}

sub getdata :Local :ActionClass('REST') {}

sub getdata_GET { 
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
#  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
  $c-&gt;detach( $c-&gt;view("JSON") ); 
}

sub postrow :Local :ActionClass('REST') {}

sub postrow_POST {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Update the template to use the new controller:</p>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
//    url:"[% c.uri_for("getdata") %]",
//    url:"[% c.uri_for("rest/getdata") %]",
    url:"[% c.uri_for("restnv/getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel :[ 
      {name:'inv_id', index:'inv_id', width:55, editable:false, key:true}, 
      {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
      {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
    ], 
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
//    editurl:"[% c.uri_for("postrow") %]" 
//    editurl:"[% c.uri_for("rest/postrow") %]" 
    editurl:"[% c.uri_for("restnv/postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Run it and the new controller should be working perfectly. You should be able to add, edit and delete rows with no problems, after all the new Restnv controller is identical to the Rest controller except for it's name: They both use Catalyst::Controller::REST with a JSON view.</p>

<p>How do we make the Restnv controller not use the JSON view? </p>

<p>The only place we render a JSON view in the Restnv controller is in the last line of the getrow_GET method:</p>

<pre><code>$c-&gt;detach( $c-&gt;view("JSON") );
</code></pre>

<p>So let's change it to one of the Catalyst::Controller::REST Status Helpers in it's documentation:</p>

<ul>
<li><code>status_ok</code>: Returns a "200 OK" response. Takes an "entity" to serialize.</li>
<li><code>status_created</code>: Returns a "201 CREATED" response. Takes an "entity" to serialize, and a "location" where the created object can be found. </li>
<li><code>status_accepted</code>: Returns a "202 ACCEPTED" response. Takes an "entity" to serialize. Also takes optional "location" for queue type scenarios. </li>
<li><code>status_no_content</code>: Returns a "204 NO CONTENT" response.</li>
<li><code>status_multiple_choices</code>: Returns a "300 MULTIPLE CHOICES" response. Takes an "entity" to serialize, which should provide list of possible locations. Also takes optional "location" for preferred choice.</li>
<li><code>status_found</code>: Returns a "302 FOUND" response. Takes an "entity" to serialize. Also takes optional "location".</li>
<li><code>status_bad_request</code>: Returns a "400 BAD REQUEST" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. </li>
<li><code>status_forbidden</code>: Returns a "403 FORBIDDEN" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. </li>
<li><code>status_not_found</code>: Returns a "404 NOT FOUND" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. </li>
<li><code>gone</code>: Returns a "41O GONE" response. Takes a "message" argument as a scalar, which will become the value of "error" in the serialized response. </li>
</ul>

<p>Change <code>getdata_GET</code> to use the <code>status_ok</code> helper:</p>

<pre><code>$self-&gt;status_ok(
  $c,
  entity =&gt; {
    rows =&gt; \@row_data,
  },
);
</code></pre>

<p>Controller/Restnv.pm:</p>

<pre><code>package MyFirstGrid::Controller::Restnv;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Restnv in Rest.');
}

sub getdata :Local :ActionClass('REST') {}

sub getdata_GET { 
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }

  $self-&gt;status_ok(
    $c,
    entity =&gt; {
      rows =&gt; \@row_data,
    },
  );  
}

sub postrow :Local :ActionClass('REST') {}

sub postrow_POST {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Run it and the grid looks good. Data is displayed and you can edit, add and delete rows—you can do everything except view the next page in the grid.</p>

<p>Oops!</p>

<p>I discovered what is wrong by using my browser's developer tools to look at my GET response. First I looked at my current response:</p>

<pre><code>{"rows":[{"cell":[37,2112,139.98,81.34,221.32,"This is record 37."]},{"cell":[36,3632,138.98,80.34,219.32,"This is record 36."]},{"cell":[35,8471,137.98,79.34,217.32,"This is record 35."]},{"cell":[34,7377,136.98,78.34,215.32,"This is record 34."]},{"cell":[33,8221,135.98,77.34,213.32,"This is record 33."]},{"cell":[32,2985,134.98,76.34,211.32,"This is record 32."]},{"cell":[31,9483,133.98,75.34,209.32,"This is record 31."]},{"cell":[30,5232,132.98,74.34,207.32,"This is record 30."]},{"cell":[29,6483,131.98,73.34,205.32,"This is record 29."]},{"cell":[28,2232,130.98,72.34,203.32,"This is record 28."]}]}
</code></pre>

<p>Then I compared this to a response that was working earlier:</p>

<pre><code>{"page":"1","records":37,"rows":[{"cell":[37,2112,139.98,81.34,221.32,"This is record 37."]},{"cell":[36,3632,138.98,80.34,219.32,"This is record 36."]},{"cell":[35,8471,137.98,79.34,217.32,"This is record 35."]},{"cell":[34,7377,136.98,78.34,215.32,"This is record 34."]},{"cell":[33,8221,135.98,77.34,213.32,"This is record 33."]},{"cell":[32,2985,134.98,76.34,211.32,"This is record 32."]},{"cell":[31,9483,133.98,75.34,209.32,"This is record 31."]},{"cell":[30,5232,132.98,74.34,207.32,"This is record 30."]},{"cell":[29,6483,131.98,73.34,205.32,"This is record 29."]},{"cell":[28,2232,130.98,72.34,203.32,"This is record 28."]}],"total":4}
</code></pre>

<p>Ahh, I am currently not providing the following:</p>

<pre><code>"page":"1","records":37,"total":4
</code></pre>

<p>The Catalyst::TraitFor::Controller::jQuery::jqGrid docs state the following:</p>

<blockquote>
  <p>The jqgrid_page method puts the 'page', 'total' and 'records' information onto the stash.</p>
</blockquote>

<p>Ahh-ha, the JSON view uses the stash but Catalyst::Controller::REST does not! We have to change this in getdata_GET:</p>

<p>Change this:</p>

<pre><code>$self-&gt;status_ok(
  $c,
  entity =&gt; {
    rows =&gt; \@row_data,
  },
);
</code></pre>

<p>To this:</p>

<pre><code>$self-&gt;status_ok($c, entity =&gt; {
  page =&gt; $c-&gt;stash-&gt;{json_data}{page},  
  records =&gt; $c-&gt;stash-&gt;{json_data}{records},  
  total =&gt; $c-&gt;stash-&gt;{json_data}{total},  
  rows =&gt; \@row_data,
});
</code></pre>

<p>Run it and you will find that everything works perfectly, our response is correct and we can page through our data on the grid:</p>

<pre><code>{"page":"1","records":37,"rows":[{"cell":[37,2112,139.98,81.34,221.32,"This is record 37."]},{"cell":[36,3632,138.98,80.34,219.32,"This is record 36."]},{"cell":[35,8471,137.98,79.34,217.32,"This is record 35."]},{"cell":[34,7377,136.98,78.34,215.32,"This is record 34."]},{"cell":[33,8221,135.98,77.34,213.32,"This is record 33."]},{"cell":[32,2985,134.98,76.34,211.32,"This is record 32."]},{"cell":[31,9483,133.98,75.34,209.32,"This is record 31."]},{"cell":[30,5232,132.98,74.34,207.32,"This is record 30."]},{"cell":[29,6483,131.98,73.34,205.32,"This is record 29."]},{"cell":[28,2232,130.98,72.34,203.32,"This is record 28."]}],"total":4}
</code></pre>

<p>Controller/Restnv:</p>

<pre><code>package MyFirstGrid::Controller::Restnv;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller::REST'}
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c-&gt;response-&gt;body('Matched MyFirstGrid::Controller::Restnv in Rest.');
}

sub getdata :Local :ActionClass('REST') {}

sub getdata_GET { 
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }

  $self-&gt;status_ok($c, entity =&gt; {
    page =&gt; $c-&gt;stash-&gt;{json_data}{page},  
    records =&gt; $c-&gt;stash-&gt;{json_data}{records},  
    total =&gt; $c-&gt;stash-&gt;{json_data}{total},  
    rows =&gt; \@row_data,
  }); 
}

sub postrow :Local :ActionClass('REST') {}

sub postrow_POST {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<h2>MySQL to SQLite</h2>

<p>As mentioned previously, this tutorial uses SQLite rather than MySQL. I will explain how you can use it too.</p>

<p>Create a SQLite database file:</p>

<pre><code>$ sqlite3 myfirstgrid.db &lt; myfirstgrid.sql
</code></pre>

<p>This creates a datebase file (myfirstgrid.db) from myfirstgrid.sql:</p>

<p>myfirstgrid.sql:</p>

<pre><code>CREATE TABLE inventory (                                                     
  inv_id integer primary key autoincrement,                                             
  client_id integer not null,                                                     
  amount real not null default '0.00',                                   
  tax real not null default '0.00',                                      
  total real not null default '0.00',                                    
  note text default null                                 
);
INSERT INTO inventory VALUES (1,2339,103.98,45.34,149.32,"This is record 1.");
INSERT INTO inventory VALUES (2,1171,104.98,46.34,151.32,"This is record 2."); 
INSERT INTO inventory VALUES (3,5232,105.98,47.34,153.32,"This is record 3."); 
INSERT INTO inventory VALUES (4,9110,106.98,48.34,155.32,"This is record 4."); 
INSERT INTO inventory VALUES (5,5232,107.98,49.34,157.32,"This is record 5."); 
INSERT INTO inventory VALUES (6,8471,108.98,50.34,159.32,"This is record 6."); 
INSERT INTO inventory VALUES (7,2339,109.98,51.34,161.32,"This is record 7."); 
INSERT INTO inventory VALUES (8,8471,110.98,52.34,163.32,"This is record 8."); 
INSERT INTO inventory VALUES (9,3632,111.98,53.34,165.32,"This is record 9."); 
INSERT INTO inventory VALUES (10,6483,112.98,54.34,167.32,"This is record 10."); 
INSERT INTO inventory VALUES (11,2333,113.98,55.34,169.32,"This is record 11."); 
INSERT INTO inventory VALUES (12,9110,114.98,56.34,171.32,"This is record 12."); 
INSERT INTO inventory VALUES (13,1005,115.98,57.34,173.32,"This is record 13."); 
INSERT INTO inventory VALUES (14,9987,116.98,58.34,175.32,"This is record 14."); 
INSERT INTO inventory VALUES (15,6483,117.98,59.34,177.32,"This is record 15."); 
INSERT INTO inventory VALUES (16,5249,118.98,60.34,179.32,"This is record 16."); 
INSERT INTO inventory VALUES (17,2333,119.98,61.34,181.32,"This is record 17."); 
INSERT INTO inventory VALUES (18,1171,120.98,62.34,183.32,"This is record 18."); 
INSERT INTO inventory VALUES (19,1005,121.98,63.34,185.32,"This is record 19."); 
INSERT INTO inventory VALUES (20,7377,122.98,64.34,187.32,"This is record 20."); 
INSERT INTO inventory VALUES (21,1171,123.98,65.34,189.32,"This is record 21."); 
INSERT INTO inventory VALUES (22,5232,124.98,66.34,191.32,"This is record 22."); 
INSERT INTO inventory VALUES (23,9110,125.98,67.34,193.32,"This is record 23."); 
INSERT INTO inventory VALUES (24,1728,126.98,68.34,195.32,"This is record 24."); 
INSERT INTO inventory VALUES (25,4732,127.98,69.34,197.32,"This is record 25."); 
INSERT INTO inventory VALUES (26,8221,128.98,70.34,199.32,"This is record 26."); 
INSERT INTO inventory VALUES (27,9110,129.98,71.34,201.32,"This is record 27."); 
INSERT INTO inventory VALUES (28,2232,130.98,72.34,203.32,"This is record 28."); 
INSERT INTO inventory VALUES (29,6483,131.98,73.34,205.32,"This is record 29."); 
INSERT INTO inventory VALUES (30,5232,132.98,74.34,207.32,"This is record 30."); 
INSERT INTO inventory VALUES (31,9483,133.98,75.34,209.32,"This is record 31."); 
INSERT INTO inventory VALUES (32,2985,134.98,76.34,211.32,"This is record 32."); 
INSERT INTO inventory VALUES (33,8221,135.98,77.34,213.32,"This is record 33."); 
INSERT INTO inventory VALUES (34,7377,136.98,78.34,215.32,"This is record 34."); 
INSERT INTO inventory VALUES (35,8471,137.98,79.34,217.32,"This is record 35."); 
INSERT INTO inventory VALUES (36,3632,138.98,80.34,219.32,"This is record 36."); 
INSERT INTO inventory VALUES (37,2112,139.98,81.34,221.32,"This is record 37.");
</code></pre>

<p>I don't know if it is necessary, but I deleted the lib/MyFirstGrid/Model and lib/MyFirstGrid/Schema directories before using the helper to recreate them for our new SQLite database. I copied myfirstgrid.db to MyFirstGrid/myfirstgrid.db. Here is the helper command to recreate the Model and Schema:</p>

<pre><code>$ script/myfirstgrid_create.pl model DB DBIC::Schema \
MyFirstGrid::Schema::DB create=static dbi:SQLite:myfirstgrid.db
</code></pre>

<h2>What's Next?</h2>

<p>Catalyst::Plugin::AutoCRUD </p>
]]>
    </content>
</entry>

<entry>
    <title>Using jqGrid with Catalyst: Tutorial with Examples for Newbies</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/j0e/2012/09/using-jqgrid-with-catalyst-tutorial-with-examples-for-newbies.html" />
    <id>tag:blogs.perl.org,2012:/users/j0e//1287.3817</id>

    <published>2012-09-12T03:52:53Z</published>
    <updated>2012-09-14T03:53:07Z</updated>

    <summary> Introduction Use jqGrid to Display a MySQL Table Get Started! Download jqGrid Download jQuery UI Theme Create MySQL Table Talk to the Browser with Two Views: Catalyst::View::TT, Catalyst::View::JSON Talk to jqGrid: Catalyst::TraitFor::Controller::jQuery::jqGrid Make a Simple &apos;edit&apos; Improve &apos;edit&apos; and...</summary>
    <author>
        <name>j0e</name>
        
    </author>
    
    <category term="perlcatalystjqgrid" label="Perl Catalyst jqGrid" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/j0e/">
        <![CDATA[<ul>
<li>Introduction</li>
<li>Use jqGrid to Display a MySQL Table</li>
<ul>
<li>Get Started!</li> 
<ul>
<li>Download jqGrid</li> 
<li>Download jQuery UI Theme</li> 
<li>Create MySQL Table</li> 
</ul>

<p><li>Talk to the Browser with Two Views: Catalyst::View::TT, Catalyst::View::JSON</li> 
<li>Talk to jqGrid: Catalyst::TraitFor::Controller::jQuery::jqGrid</li> 
</ul>
<li>Make a Simple 'edit'</li>
<li>Improve 'edit' and Add 'add' and 'delete'</li>
<li>Final Version</li> 
<li>Thanks</li> 
<li>About</li> 
</ul></p>
]]>
        <![CDATA[<h1>Introduction</h1>

<p>I am a Perl Newbie. I hope this tutorial is helpful to Newbies interested in using jqGrid with Catalyst.</p>

<p>From the jqGrid documentation:</p>

<p><a href="http://www.trirand.com/jqgridwiki/doku.php">http://www.trirand.com/jqgridwiki/doku.php</a> </p>

<p>jqGrid is an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web. Since the grid is a client-side solution loading data dynamically through Ajax callbacks, it can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.</p>

<p>jqGrid uses a jQuery JavaScript Library and is written as a plugin for that package. For more information on jQuery, please refer to the jQuery web site:</p>

<p><a href="http://jquery.com">http://jquery.com</a> </p>

<p>jqGrid's Home page can be found here:</p>

<p><a href="http://www.trirand.com">http://www.trirand.com</a> </p>

<p>Working examples of jqGrid, with explanations, can be found here:</p>

<p><a href="http://www.trirand.com/blog/jqgrid/jqgrid.html">http://www.trirand.com/blog/jqgrid/jqgrid.html</a></p>

<p>The last development version can be obtained from GitHub:</p>

<p><a href="https://github.com/tonytomov/jqGrid">https://github.com/tonytomov/jqGrid</a> </p>

<h1>Use jqGrid to Display a MySQL Table</h1>

<h2>Get Started!</h2>

<p>As simply as possible we will display a MySQL table with jqGrid. We first download jqGrid, then create a MySQL table. I'll provide a script to create and populate the table with data.</p>

<h3>Download jqGrid</h3>

<p>The following link will take you to the jqGrid homepage where you can select "Downloads". You may not want to for other projects, but for this one select and download all components:</p>

<p><a href="http://www.trirand.com">http://www.trirand.com</a></p>

<p>I am using jqGrid version 4.4.1.</p>

<h3>Download jQuery UI Theme</h3>

<p>You can roll your own jQuery UI theme or select from a gallery. For this project select the Cupertino theme from the gallery and download all components:</p>

<p><a href="http://jqueryui.com/themeroller">http://jqueryui.com/themeroller</a> </p>

<p>I am using jQuery-UI version 1.8.22.</p>

<h3>Create MySQL Table</h3>

<p>Use the following script to create your database for this tutorial:</p>

<p>myfirstgrid01.sql:</p>

<pre><code>CREATE TABLE inventory (                                                     
  inv_id int(11) NOT NULL AUTO_INCREMENT,                                             
  client_id int(11) NOT NULL,                                                     
  amount decimal(10,2) NOT NULL DEFAULT '0.00',                                   
  tax decimal(10,2) NOT NULL DEFAULT '0.00',                                      
  total decimal(10,2) NOT NULL DEFAULT '0.00',                                    
  note char(100) DEFAULT NULL,                                 
  PRIMARY KEY  (inv_id) 
);
INSERT INTO inventory(
  inv_id,
  client_id,
  amount,
  tax,
  total,
  note)
VALUES
  (1,2339,103.98,45.34,149.32,"This is record 1."),
  (2,1171,104.98,46.34,151.32,"This is record 2."), 
  (3,5232,105.98,47.34,153.32,"This is record 3."), 
  (4,9110,106.98,48.34,155.32,"This is record 4."), 
  (5,5232,107.98,49.34,157.32,"This is record 5."), 
  (6,8471,108.98,50.34,159.32,"This is record 6."), 
  (7,2339,109.98,51.34,161.32,"This is record 7."), 
  (8,8471,110.98,52.34,163.32,"This is record 8."), 
  (9,3632,111.98,53.34,165.32,"This is record 9."), 
  (10,6483,112.98,54.34,167.32,"This is record 10."), 
  (11,2333,113.98,55.34,169.32,"This is record 11."), 
  (12,9110,114.98,56.34,171.32,"This is record 12."), 
  (13,1005,115.98,57.34,173.32,"This is record 13."), 
  (14,9987,116.98,58.34,175.32,"This is record 14."), 
  (15,6483,117.98,59.34,177.32,"This is record 15."), 
  (16,5249,118.98,60.34,179.32,"This is record 16."), 
  (17,2333,119.98,61.34,181.32,"This is record 17."), 
  (18,1171,120.98,62.34,183.32,"This is record 18."), 
  (19,1005,121.98,63.34,185.32,"This is record 19."), 
  (20,7377,122.98,64.34,187.32,"This is record 20."), 
  (21,1171,123.98,65.34,189.32,"This is record 21."), 
  (22,5232,124.98,66.34,191.32,"This is record 22."), 
  (23,9110,125.98,67.34,193.32,"This is record 23."), 
  (24,1728,126.98,68.34,195.32,"This is record 24."), 
  (25,4732,127.98,69.34,197.32,"This is record 25."), 
  (26,8221,128.98,70.34,199.32,"This is record 26."), 
  (27,9110,129.98,71.34,201.32,"This is record 27."), 
  (28,2232,130.98,72.34,203.32,"This is record 28."), 
  (29,6483,131.98,73.34,205.32,"This is record 29."), 
  (30,5232,132.98,74.34,207.32,"This is record 30."), 
  (31,9483,133.98,75.34,209.32,"This is record 31."), 
  (32,2985,134.98,76.34,211.32,"This is record 32."), 
  (33,8221,135.98,77.34,213.32,"This is record 33."), 
  (34,7377,136.98,78.34,215.32,"This is record 34."), 
  (35,8471,137.98,79.34,217.32,"This is record 35."), 
  (36,3632,138.98,80.34,219.32,"This is record 36."), 
  (37,2112,139.98,81.34,221.32,"This is record 37.");
</code></pre>

<p>Note: When I used it, the jqGrid tutorial provided a script with dates Perl tries to use as DateTime objects. There are two reasons why I chose to not use them: 1) I want my Perl code to be as simple as possible 2) MySQL wants dates to be formatted YY/MM/DD. The jqGrid tutorial's date is formatted MM/DD/YY—I didn't want this to be a problem to solve at this time.</p>

<p>These are the commands I used to create the database:</p>

<pre><code>$ mysql -uusername -ppassword

mysql&gt; create database myfirstgrid01; 

mysql&gt; use myfirstgrid01; 

mysql&gt; source myfirstgrid01.sql;
</code></pre>

<p>You can take a look at the database if you want to:</p>

<pre><code>mysql&gt; show tables;
+-------------------------+
| Tables_in_myfirstgrid01 |
+-------------------------+
| inventory               |
+-------------------------+
1 row in set (0.00 sec)

mysql&gt; describe inventory;
+-----------+---------------+------+-----+---------+----------------+
| Field     | Type          | Null | Key | Default | Extra          |
+-----------+---------------+------+-----+---------+----------------+
| inv_id    | int(11)       | NO   | PRI | NULL    | auto_increment |
| client_id | int(11)       | NO   |     | NULL    |                |
| amount    | decimal(10,2) | NO   |     | 0.00    |                |
| tax       | decimal(10,2) | NO   |     | 0.00    |                |
| total     | decimal(10,2) | NO   |     | 0.00    |                |
| note      | char(100)     | YES  |     | NULL    |                |
+-----------+---------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

mysql&gt; select * from inventory;
+--------+-----------+--------+-------+--------+--------------------+
| inv_id | client_id | amount | tax   | total  | note               |
+--------+-----------+--------+-------+--------+--------------------+
|      1 |      2339 | 103.98 | 45.34 | 149.32 | This is record 1.  |
|      2 |      1171 | 104.98 | 46.34 | 151.32 | This is record 2.  |
|      3 |      5232 | 105.98 | 47.34 | 153.32 | This is record 3.  |
|      4 |      9110 | 106.98 | 48.34 | 155.32 | This is record 4.  |
|      5 |      5232 | 107.98 | 49.34 | 157.32 | This is record 5.  |
|      6 |      8471 | 108.98 | 50.34 | 159.32 | This is record 6.  |
|      7 |      2339 | 109.98 | 51.34 | 161.32 | This is record 7.  |
|      8 |      8471 | 110.98 | 52.34 | 163.32 | This is record 8.  |
|      9 |      3632 | 111.98 | 53.34 | 165.32 | This is record 9.  |
|     10 |      6483 | 112.98 | 54.34 | 167.32 | This is record 10. |
|     11 |      2333 | 113.98 | 55.34 | 169.32 | This is record 11. |
|     12 |      9110 | 114.98 | 56.34 | 171.32 | This is record 12. |
|     13 |      1005 | 115.98 | 57.34 | 173.32 | This is record 13. |
|     14 |      9987 | 116.98 | 58.34 | 175.32 | This is record 14. |
|     15 |      6483 | 117.98 | 59.34 | 177.32 | This is record 15. |
|     16 |      5249 | 118.98 | 60.34 | 179.32 | This is record 16. |
|     17 |      2333 | 119.98 | 61.34 | 181.32 | This is record 17. |
|     18 |      1171 | 120.98 | 62.34 | 183.32 | This is record 18. |
|     19 |      1005 | 121.98 | 63.34 | 185.32 | This is record 19. |
|     20 |      7377 | 122.98 | 64.34 | 187.32 | This is record 20. |
|     21 |      1171 | 123.98 | 65.34 | 189.32 | This is record 21. |
|     22 |      5232 | 124.98 | 66.34 | 191.32 | This is record 22. |
|     23 |      9110 | 125.98 | 67.34 | 193.32 | This is record 23. |
|     24 |      1728 | 126.98 | 68.34 | 195.32 | This is record 24. |
|     25 |      4732 | 127.98 | 69.34 | 197.32 | This is record 25. |
|     26 |      8221 | 128.98 | 70.34 | 199.32 | This is record 26. |
|     27 |      9110 | 129.98 | 71.34 | 201.32 | This is record 27. |
|     28 |      2232 | 130.98 | 72.34 | 203.32 | This is record 28. |
|     29 |      6483 | 131.98 | 73.34 | 205.32 | This is record 29. |
|     30 |      5232 | 132.98 | 74.34 | 207.32 | This is record 30. |
|     31 |      9483 | 133.98 | 75.34 | 209.32 | This is record 31. |
|     32 |      2985 | 134.98 | 76.34 | 211.32 | This is record 32. |
|     33 |      8221 | 135.98 | 77.34 | 213.32 | This is record 33. |
|     34 |      7377 | 136.98 | 78.34 | 215.32 | This is record 34. |
|     35 |      8471 | 137.98 | 79.34 | 217.32 | This is record 35. |
|     36 |      3632 | 138.98 | 80.34 | 219.32 | This is record 36. |
|     37 |      2112 | 139.98 | 81.34 | 221.32 | This is record 37. |
+--------+-----------+--------+-------+--------+--------------------+
37 rows in set (0.00 sec)
</code></pre>

<h2>Talk to the Browser with Two Views: Catalyst::View::TT, Catalyst::View::JSON</h2>

<p>Let's get started creating our Catalyst application:</p>

<pre><code>$ catalyst.pl MyFirstGrid 
$ cd MyFirstGrid
$ perl Makefile.PL
$ script/myfirstgrid_create.pl view TT TT 
$ script/myfirstgrid_create.pl view JSON JSON 
$ script/myfirstgrid_create.pl model DB DBIC::Schema \
&gt; MyFirstGrid::Schema::DB create=static \
&gt; dbi:mysql:myfirstgrid01 username password
</code></pre>

<p>We need to specify which stash keys are exposed as a JSON response. I did so in the application class:</p>

<p>MyFirstGrid.pm:</p>

<pre><code>__PACKAGE__-&gt;config({
  'View::JSON' =&gt; {
    expose_stash =&gt; qw(json_data)
  }
});
</code></pre>

<p>(See the Catalyst::View::JSON docs for details.)</p>

<h2>Talk to jqGrid: Catalyst::TraitFor::Controller::jQuery::jqGrid</h2>

<p>This is where we start making Catalyst magic happen.</p>

<p>Without Catalyst::TraitFor::Controller::jQuery::jqGrid we would need to calculate jqGrid parameters to find data corresponding to jqGrid pages, then find, prepare and send it to jqGrid for display in our browser. Catalyst::TraitFor::Controller::jQuery::jqGrid does most of this for us.</p>

<p>If I understand Catalyst correctly, Catalyst::TraitFor::Controller::jQuery::jqGrid is a Moose role, which we use in our controller like this:</p>

<pre><code>BEGIN { extends 'Catalyst::Controller' }
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid';
</code></pre>

<h1>Make a Simple 'edit'</h1>

<p>First we will install jqGrid into our Catalyst application:</p>

<p><img alt="MyFirstGrid01.png" src="http://blogs.perl.org/users/j0e/MyFirstGrid01.png" width="308" height="344" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></p>

<p>'root/static/css' and 'root/static/js' contain jqGrid files:</p>

<p><img alt="MyFirstGrid02.png" src="http://blogs.perl.org/users/j0e/MyFirstGrid02.png" width="137" height="135" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></p>

<p>MyFirstGrid application classes:</p>

<p><img alt="MyFirstGrid00.png" src="http://blogs.perl.org/users/j0e/MyFirstGrid00.png" width="230" height="394" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></p>

<p>Once we get this far the remaining focus of our work will be with our template we will now create, our root controller, and using our Catalyst console and browser developer tools to watch Catalyst and jqGrid talk to each other.</p>

<p>Create the following template:</p>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 
  $("#list").jqGrid({
    url:"[% c.uri_for("getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel:[ 
      {name:'inv_id', index:'inv_id', width:55}, 
      {name:'client_id', index:'client_id', width:55},
      {name:'amount', index:'amount', width:80, align:'right'}, 
      {name:'tax', index:'tax', width:80, align:'right'}, 
      {name:'total', index:'total', width:80, align:'right'}, 
      {name:'note', index:'note', width:150, sortable:false} 
    ],
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid'
  }); 
}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>This template is only slightly modified from the jqGrid tutorial MyFirstGrid:</p>

<p><a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:first_grid">http://www.trirand.com/jqgridwiki/doku.php?id=wiki:first_grid</a> </p>

<p>Consider the settings and options used in our code:</p>

<table class="inline">
    <tr class="row1">
        <td class="col0"> url </td><td class="col1"> Tells us where to get the data. Typically this is a server-side function with a connection to a database which returns the appropriate information to be filled into the Body layer in the grid </td>
    </tr>
    <tr class="row2">
        <td class="col0"> datatype </td><td class="col1"> This tells jqGrid the type of information being returned so it can construct the grid. In this case, we tell the grid that we expect <acronym title="Extensible Markup Language">XML</acronym> data to be returned from the server, but other formats are possible. For a list of all available datatypes refer to <acronym title="Application Programming Interface">API</acronym> Methods </td>
    </tr>
    <tr class="row3">
        <td class="col0 leftalign"> mtype   </td><td class="col1"> Tells us how to make the Ajax call: either &#039;GET&#039; or &#039;POST&#039;. In this case, we will use the GET method to retrieve data from the server </td>
    </tr>
    <tr class="row4">
        <td class="col0"> colNames </td><td class="col1">An array in which we place the names of the columns. This is the text that appears in the head of the grid (Header layer). The names are separated with commas </td>
    </tr>
    <tr class="row5">
        <td class="col0"> colModel </td><td class="col1">An array that describes the model of the columns. This is the most important part of the grid. Here I explain only the options used above. For the complete list of options see colModel <acronym title="Application Programming Interface">API</acronym> <br/>
 <strong>name</strong>: The name of the column. This name does not have to be the name from the database table, but later we will see how we can use this when we have different data formats. <br/>
 <strong>index</strong>: The name passed to the server on which to sort the data (note that we could pass column numbers instead). Typically this is the name (or names) from the database – this is server-side sorting, so what you pass depends on what your server expects to receive.  <br/>
 <strong>width</strong>: The width of the column, in pixels. <br/>
 <strong>align</strong>: The alignment of the column. <br/>
 <strong>sortable</strong>: Specifies if the data in the grid can be sorted on this column; if false, clicking on the header has no effect.</td>
    </tr>
    <tr class="row6">
        <td class="col0">pager</td><td class="col1">Defines that we want to use a pager bar to navigate through the records. This must be a valid <acronym title="HyperText Markup Language">HTML</acronym> element; in our example we gave the div the id of “pager”, but any name is acceptable. Note that the Navigation layer (the “pager” div) can be positioned anywhere you want, determined by your <acronym title="HyperText Markup Language">HTML</acronym>; in our example we specified that the pager will appear after the Body layer.</td>
    </tr>
    <tr class="row7">
        <td class="col0">rowNum</td><td class="col1">Sets how many records we want to view in the grid. This parameter is passed to the <acronym title="Uniform Resource Locator">URL</acronym> for use by the server routine retrieving the data</td>
    </tr>
    <tr class="row8">
        <td class="col0"> rowList</td><td class="col1">An array to construct a select box element in the pager in which we can change the number of the visible rows. When changed during the execution, this parameter replaces the rowNum parameter that is passed to the url</td>
    </tr>
    <tr class="row9">
        <td class="col0">sortname</td><td class="col1">Sets the initial sorting column. Can be a name or number. This parameter is added to the <acronym title="Uniform Resource Locator">URL</acronym> for use by the server routine</td>
    </tr>
    <tr class="row10">
        <td class="col0">viewrecords</td><td class="col1">Defines whether we want to display the number of total records from the query in the pager bar</td>
    </tr>
    <tr class="row11">
        <td class="col0">caption</td><td class="col1">Sets the caption for the grid. If this parameter is not set the Caption layer will be not visible</td>
    </tr>
</table>

<pre><code>package MyFirstGrid::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ($self, $c) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub getdata :Local {
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
}

sub default :Path {
  my ($self, $c) = @_;

  $c-&gt;response-&gt;body('Page not found');
  $c-&gt;response-&gt;status(404);
}

# Attempt to render a view, if needed.

sub end :ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<p>Before I mention a few things in the controller, let's run the application:</p>

<pre><code>$ ./script/myfirstgrid_server.pl -d -r -k
</code></pre>

<p>When we open our browser with http://0.0.0.0:3000/ our Catalyst console gives us our browser's GET request: </p>

<pre><code>[debug] "GET" request for "getdata" from "127.0.0.1"
[debug] Query Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| _search                             | false                                |
| nd                                  | 1347390094273                        |
| page                                | 1                                    |
| rows                                | 10                                   |
| sidx                                | inv_id                               |
| sord                                | desc                                 |
'-------------------------------------+--------------------------------------'
</code></pre>

<p>We should see our grid filled with data in the browser, but we don't yet have the ability to edit it.</p>

<p>We need to edit our template to call editurl and setup it's options:</p>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
    url:"[% c.uri_for("getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel:[ 
      {name:'inv_id', index:'inv_id', width:55}, 
      {name:'client_id', index:'client_id', width:55},
      {name:'amount', index:'amount', width:80, align:'right'}, 
      {name:'tax', index:'tax', width:80, align:'right'}, 
      {name:'total', index:'total', width:80, align:'right'}, 
      {name:'note', index:'note', width:150, sortable:false} 
    ],
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
    editurl:"[% c.uri_for("postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>And we need to add the postrow method to the root controller:</p>

<p>Controller/Root.pm:</p>

<pre><code>sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;req-&gt;params;
  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{inv_id}});
  $inv_rs-&gt;update({
    client_id =&gt; $data-&gt;{client_id},
    amount =&gt; $data-&gt;{amount},
    tax =&gt; $data-&gt;{tax}, 
    total =&gt; $data-&gt;{total}, 
    note =&gt; $data-&gt;{note}, 
  });
  $c-&gt;response-&gt;status(204); 
}
</code></pre>

<p>Run it and see what you get.</p>

<p>You should see a change at the bottom of the grid, with the addition of add, edit, delete, search and refresh icons.</p>

<p>Select a row by clicking on it, then select the edit icon and submit the row to be saved. Note you were not allowed to edit anything and the POST did not provide any parameters other than 'oper':</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| oper                                | edit                                 |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>I wanted it to do this on purpose. I want you to see that parameters POSTed correspond to jqGrid columns that are editable. In our template we now have:</p>

<pre><code>colModel:[ 
  {name:'inv_id', index:'inv_id', width:55}, 
  {name:'client_id', index:'client_id', width:55},
  {name:'amount', index:'amount', width:80, align:'right'}, 
  {name:'tax', index:'tax', width:80, align:'right'}, 
  {name:'total', index:'total', width:80, align:'right'}, 
  {name:'note', index:'note', width:150, sortable:false} 
],
</code></pre>

<p>Simply making them editable should cause them to be POSTed for use by our controller:</p>

<pre><code>colModel :[ 
  {name:'inv_id', index:'inv_id', width:55, editable:true, editoptions:{size:10}}, 
  {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
  {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
],
</code></pre>

<p>Making these changes and running the application, the parameters we need are now POSTed and we are able to edit our data:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 135.00                               |
| client_id                           | 8221                                 |
| id                                  | 6                                    |
| inv_id                              | 33                                   |
| note                                | This is record 33.                   |
| oper                                | edit                                 |
| tax                                 | 77.34                                |
| total                               | 213.32                               |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>Here is our template and controller:</p>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
    url:"[% c.uri_for("getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel :[ 
      {name:'inv_id', index:'inv_id', width:55, editable:true, editoptions:{size:10}}, 
      {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
      {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
    ], 
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
    editurl:"[% c.uri_for("postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Controller/Root.pm:</p>

<pre><code>package MyFirstGrid::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ($self, $c) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub getdata :Local {
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
}

sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;req-&gt;params;
  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{inv_id}});
  $inv_rs-&gt;update({
    client_id =&gt; $data-&gt;{client_id},
    amount =&gt; $data-&gt;{amount},
    tax =&gt; $data-&gt;{tax}, 
    total =&gt; $data-&gt;{total}, 
    note =&gt; $data-&gt;{note}, 
  });
  $c-&gt;response-&gt;status(204); 
} 

sub default :Path {
  my ($self, $c) = @_;

  $c-&gt;response-&gt;body('Page not found');
  $c-&gt;response-&gt;status(404);
}

# Attempt to render a view, if needed.

sub end :ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<h1>Improve 'edit' and Add 'add' and 'delete'</h1>

<p>Notice what is POSTed when we try to add a row:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 2                                    |
| client_id                           | 2                                    |
| id                                  | _empty                               |
| inv_id                              | 2                                    |
| note                                | 2                                    |
| oper                                | add                                  |
| tax                                 | 2                                    |
| total                               | 2                                    |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>We are almost getting what we need POSTed, but we are going to make a few changes. First of all, editing <code>inv_id</code> is not something we should be able to do, so let's make <code>inv_id</code> not editable:</p>

<pre><code>colModel :[ 
  {name:'inv_id', index:'inv_id', width:55, editable:false}, 
  {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
  {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
],
</code></pre>

<p>Let's run it and see what we get now when we add a row:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 2                                    |
| client_id                           | 2                                    |
| id                                  | _empty                               |
| note                                | 2                                    |
| oper                                | add                                  |
| tax                                 | 2                                    |
| total                               | 2                                    |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>We no longer are able to edit <code>inv_id</code> so let's put our code to add rows into our controller:</p>

<pre><code>sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
}
</code></pre>

<p>Add a new row and see what you get:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 2                                    |
| client_id                           | 2                                    |
| id                                  | _empty                               |
| note                                | 2                                    |
| oper                                | add                                  |
| tax                                 | 2                                    |
| total                               | 2                                    |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>The POST looks good, and you should be able to page through your data and observe that the new row looks OK. But try to edit a row and when you submit it you will have problems. Take a look at my console:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 2.00                                 |
| client_id                           | 2222                                 |
| id                                  | 1                                    |
| note                                | 2                                    |
| oper                                | edit                                 |
| tax                                 | 2.00                                 |
| total                               | 2.00                                 |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>The 'id' parameter is of no use to me, I need <code>inv_id</code> to save the correct row. 'id' is the index jqGrid uses to identify a particular row in the grid.</p>

<p>Thanks to Oleg at <a href="http://stackoverflow.com">http://stackoverflow.com</a> we have a solution. Set key:true in jqGrid's <code>inv_id</code> definition so that jqGrid POSTs <code>inv_id</code> rather than 'id':</p>

<pre><code>colModel :[ 
  {name:'inv_id', index:'inv_id', width:55, editable:false, key:true}, 
  {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
  {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
  {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
],
</code></pre>

<p>Edit a row and the parameter POSTed is still named 'id', but now it's value is that of <code>inv_id</code> for that row:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| amount                              | 2.00                                 |
| client_id                           | 2222                                 |
| id                                  | 39                                   |
| note                                | 2                                    |
| oper                                | edit                                 |
| tax                                 | 2.00                                 |
| total                               | 2.00                                 |
'-------------------------------------+--------------------------------------'
[debug] Response Code: 204;
</code></pre>

<p>Adding a row should still work too, but now see what happens when you try to delete one:</p>

<pre><code>[debug] "POST" request for "postrow" from "127.0.0.1"
[debug] Body Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| id                                  | 39                                   |
| oper                                | del                                  |
'-------------------------------------+--------------------------------------'
[warn] Calling $c-&gt;view() will return a random view unless you specify one of:
[warn] * $c-&gt;config(default_view =&gt; "the name of the default view to use")
[warn] * $c-&gt;stash-&gt;{current_view} # the name of the view to use for this request
[warn] * $c-&gt;stash-&gt;{current_view_instance} # the instance of the view to use for this request
[warn] NB: in version 5.81, the "random" behavior will not work at all.
[debug] Rendering template "postrow.tt"
[error] Couldn't render template "postrow.tt: file error - postrow.tt: not found"
[error] Couldn't render template "postrow.tt: file error - postrow.tt: not found"
[debug] Response Code: 500;
</code></pre>

<p>The POST parameters are correct, let's add the code to the controller. Add the 'delete' condition to postrow:</p>

<pre><code>elsif ($data-&gt;{oper} eq 'del') { # delete row
  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
  $inv_rs-&gt;delete();
  $c-&gt;response-&gt;status(204); 
}
else {
  $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
  $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
}
</code></pre>

<p>We now have:</p>

<pre><code>sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
}
</code></pre>

<h1>Final Version</h1>

<p>root/index.tt:</p>

<pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;My First Grid&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/cupertino/jquery-ui-1.8.22.custom.css') %]" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/ui.jqgrid.css') %]" /&gt;

&lt;style type="text/css"&gt;
html, body {
  margin: 0;
  padding: 0;
  font-size: 75%;
}
&lt;/style&gt;

&lt;script src="[% c.uri_for('/static/js/jquery-1.7.2.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/i18n/grid.locale-en.js') %]" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="[% c.uri_for('/static/js/jquery.jqGrid.min.js') %]" type="text/javascript"&gt;&lt;/script&gt;

&lt;script type="text/javascript"&gt;
$(function(){ 

  $("#list").jqGrid({
    url:"[% c.uri_for("getdata") %]",
    datatype:'json',
    mtype:'GET',
    colNames:['Inv No', 'Client ID', 'Amount','Tax','Total','Notes'],
    colModel :[ 
      {name:'inv_id', index:'inv_id', width:55, editable:false, key:true}, 
      {name:'client_id', index:'client_id', width:55, editable:true, editoptions:{size:10}},
      {name:'amount', index:'amount', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'tax', index:'tax', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'total', index:'total', width:80, align:'right', editable:true, editoptions:{size:10}}, 
      {name:'note', index:'note', width:150, sortable:false, editable: true, edittype:"textarea", editoptions:{rows:"2",cols:"20"}} 
    ], 
    pager:'#pager',
    rowNum:10,
    rowList:[10,20,30],
    sortname:'inv_id',
    sortorder:'desc',
    viewrecords:true,
    gridview:true,
    caption:'My First Grid',
    editurl:"[% c.uri_for("postrow") %]" 
  }); 

  jQuery("#list").jqGrid('navGrid','#pager',
    {}, //options
    {height:280,reloadAfterSubmit:false}, // edit options
    {height:280,reloadAfterSubmit:false}, // add options
    {reloadAfterSubmit:false}, // del options
    {} // search options
  ); 

}); 
&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;table id="list"&gt;&lt;tr&gt;&lt;td/&gt;&lt;/tr&gt;&lt;/table&gt; 
&lt;div id="pager"&gt;&lt;/div&gt; 
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Controller/Root.pm:</p>

<pre><code>package MyFirstGrid::Controller::Root;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }
with 'Catalyst::TraitFor::Controller::jQuery::jqGrid'; 

__PACKAGE__-&gt;config(namespace =&gt; '');

sub index :Path :Args(0) {
  my ($self, $c) = @_;

  $c-&gt;detach($c-&gt;view("TT"));
}

sub getdata :Local {
  my ($self, $c) = @_;

  my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({});
  $inv_rs = $self-&gt;jqgrid_page($c, $inv_rs);
  my @row_data;
  while (my $inv = $inv_rs-&gt;next) {
    my $single_row = {
      cell =&gt; [
        $inv-&gt;inv_id,
        $inv-&gt;client_id,
        $inv-&gt;amount,
        $inv-&gt;tax,
        $inv-&gt;total,
        $inv-&gt;note,
      ],
    };
    push @row_data, $single_row;
  }
  $c-&gt;stash-&gt;{json_data}{rows} = \@row_data;
  $c-&gt;stash-&gt;{current_view} = 'JSON'; 
}

sub postrow :Local {
  my ($self, $c) = @_;

  my $data = $c-&gt;request-&gt;parameters;
  if ($data-&gt;{oper} eq 'edit') { # save row 
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;update({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }
  elsif ($data-&gt;{oper} eq 'add') { # add new row
    my $inv_rs = $c-&gt;model('DB::Inventory');
    $inv_rs-&gt;create({
      client_id =&gt; $data-&gt;{client_id},
      amount =&gt; $data-&gt;{amount},
      tax =&gt; $data-&gt;{tax}, 
      total =&gt; $data-&gt;{total}, 
      note =&gt; $data-&gt;{note}, 
    });
    $c-&gt;response-&gt;status(204); 
  }  
  elsif ($data-&gt;{oper} eq 'del') { # delete row
    my $inv_rs = $c-&gt;model('DB::Inventory')-&gt;search({inv_id =&gt; $data-&gt;{id}});
    $inv_rs-&gt;delete();
    $c-&gt;response-&gt;status(204); 
  }
  else {
    $c-&gt;response-&gt;body('400 BAD REQUEST: Root/postrow');
    $c-&gt;response-&gt;status(400); # 400 BAD REQUEST 
  } 
} 

sub default :Path {
  my ($self, $c) = @_;

  $c-&gt;response-&gt;body('Page not found');
  $c-&gt;response-&gt;status(404);
}

# Attempt to render a view, if needed.

sub end :ActionClass('RenderView') {}

__PACKAGE__-&gt;meta-&gt;make_immutable;

1;
</code></pre>

<h1>Thanks</h1>

<p>Thanks to the authors of the llama and alpaca books, the Pittsburgh Perl Workshop, and the developers of Perldoc::Server for helping me get started.</p>

<p>Thanks to t0m for his "Heavy Tools", and others at irc.perl.org who've helped me begin to learn Perl: rafl, kd, osfameron (moof!), gshank, mst, dpetrov, hobbs, joel.</p>

<p>Thanks to Devin Austin for his jqGrid and Catalyst::Controller::REST tutorial in the Catalyst Advent Calender, and for Grimlock::Web and correspondence.</p>

<p>Thanks to Oleg at http://stackoverflow.com for help with jqGrid.</p>

<h1>About</h1>

<p>My name is Nolan Joseph Axford and I live in Dover-Foxcroft, Maine—you can call me j0e. As of September 11th, 2012 I am 52 years old learning Perl after coding real-time embedded systems with Assembly and C. It's been 10 years since I wrote any code. My Perl skills are a speck of sand on a very large beach, I'm a 95 lb. Perl weakling with sand blasting into my face.</p>

<p>I am trying to gain enough Perl skill to find work. I would especially like to try living and working outside the United States.</p>
]]>
    </content>
</entry>

</feed>
