Global find and replace in a svn tree
Forwards don't seem to work properly
Catalyst::Plugin::Session::FastMmap
Classes that are defined via Catalyst::Model::CDBI or Catalyst::Model::CDBI::Sweet
My Template Toolkit FOREACH loop yields no output, but the data is in the stash
Ampersand (&) in links breaks query string
Authentication::CDBI login causes "Can't locate object method "req" via package..."
Installing MakeMaker *and* Module::Build modules to your homedir with CPAN
What does "PREFIX not supported" mean?
How do I install to my home directory?
Installing Catalyst as someone other than root
Getting the most from Class::DBI::Loader
Getting the most from DBIx::Class::Loader
Character set issues with Catalyst::View::TT
Error handling
Apache2 performance is bad under load
The application home directory is not properly detected
Non-root (shared hosting) apache configuration
Can't locate object method "storage" via package ...
CGI.pm reports "client aborted" errors with file uploads
"Can't locate method 'handler'..."-like error messages when trying to run a Catalyst application under apache2/mod_perl2 (win32)
Installing Catalyst under Gentoo
Configure first, setup later
Quickly installing Catalyst
Deploying using PAR + Perl Packager on shared host with old perl (5.6.1) and no root
Apache/mod_perl segfaults when loading Catalyst app using MySQL
Installation under windows with Active State and PPM
Apache 1/2 + mod_fastcgi / external server - missing preceeding slash
Exempt specific pages from cacheing
Logins with case insensitive usernames

Global find and replace in a subversion tree

Say you want to remove debug breakpoints from your code, but optionally put them back later, but you don't want to contaminate your .svn directories. This is what I did:

find . -name '*pm' \! -name '*.svn*' | xargs perl -p -i -e 's/(\^\s?$DB::single\s?=\s?\d+)/# $1/'

Forwards don't seem to work properly

Recently, I had defined an action as Path('/active_orders'). The action appeared in the server debug output. When I attempted either of the following:

   $c->forward('/active_orders');
   $c->forward('active_orders');

I got the error message:

   Couldn't forward to command "active_orders". Invalid action or component.

The final solution was to sidestep the problem by redefining this as a Local action and changing the URL I used to call it.

*Note that you should forward to the action's *private* name, not the one the url matches.* This name shows up in the table when you start your application.

-- Marcus


Catalyst::Plugin::Session::FastMmap?

Note that under Catalyst::Plugin::Session session data is dumped, and the documentation is slightly better

It appeared that my sessions were not holding state between requests. Actually, they were working fine and I was confused about how to use the plugin. Here is what you need to know:

  • Put the following in your App->config call:
    session => { 
        expires => '3600',         # This is automagically changed to '+3600s'
        rewrite => 0,              # Or 1, if you want the session ID to be in the URL
        storage => "/tmp/session", # Or whatever path you prefer
    },   
  • Note that the session data is stored server-side; it will not appear on the debug page when you die(). In order to review it, do this:
  use Data::Dumper;

  $c->log->debug("session contains: " . Dumper $c->session);
  • If you want to read or write from the session, make sure you access the server-side object, not the client-side cookie:
    # In some action somewhere...

    #  We need to store the current date in the session:
    $c->req->cookies->{session}->{date} = '20050807';  # WRONG!! THIS WILL NOT PERSIST.
    $c->session->{date} = '20050807';                  # Right!


    #  Later, in another action    
    if ($c->session->{date} > $SOME_VALUE_DEFINED_ELSEWHERE) {
       ...
    }    

Classes that are defined via Catalyst::Model::CDBI or Catalyst::Model::CDBI::Sweet

I keep getting this message: Caught exception "Can't locate object method "<FIELD_NAME>" via package "App::M::<TABLE_NAME>"".

First thing to check: did you spell the module name correctly? A lot of times I have left off a final 's', like so:

   App::M::CustomerProfile->foo   #  Oddly enough, this doesn't work
   App::M::CustomerProfiles->foo   #  Ah, this one does.

This issue seems pretty straightforward, but I've made this mistake often enough that I thought I'd include it for others.

This is why I prefer (when building something from scratch where I have this kind of control) to give the tables in the database singular names. When building classes using Class::DBI::Loader or something similar you have to deal with the fact that either the database will contain singular names that logically seem like they should be plural (customer vs. customers), or the code will contain class names that are plural when your brain says they should be singular (MyApp::M::CustomerProfile vs. MyApp::M::CustomerProfiles). Since I work with the code a lot more than I work with the database schema directly, I prefer to leave the confusion all in one spot, in the database.

If this still doesn't work, and you are using Catalyst::Model::CDBI::Sweet, try switching to Catalyst::Model::CDBI, and add the following to config in your class:

   left_base_classes => [ 'Class::DBI::Sweet' ],

My Template Toolkit FOREACH loop yields no output, but the data is in the stash

If you have FOREACH x in xyz try changing it to FOREACH x IN xyz--the capitalization of 'IN' is important. (You can also avoid this using FOREACH x = xyz.)


Ampersand (&) in links breaks query string

The TT filters html and uri are no solution to this problem. The first one transforms an & to &amp;, leaving the & inside, and so the problem. The latter ignores ampersands, because they are valid in URIs. As a solution I replaced it manually:

[% myvar | replace( '&', '%26' ) %]

Note that if you've already transferred the ampersand into a html-entity (namely &amp;) you have to replace that sequence of characters, or you're left with '%26amp;'.

Replacing & with &amp; is correct in URLs, according to the XHTML standard.

-- Sure, but this get's a problem if you get http://example.com/action/?a=1&amp;par2=http://other.example.com/?a=2&amp;b=5

-- This problem only occurs if you are converting & to &amp;, and something else is also doing it, meaning you end up with &amp;amp; in the HTML source. You ought to check whether whatever you are using for the view automatically HTMLizes output for you.

-- It's better to use ; as a delimiter in URIs instead of &

-- Try using Template::Plugin::URL


Authentication::CDBI login causes "Can't locate object method "req" via package..."

try renaming your login method from 'login' in main application class (it overrides the Auth login method).


Installing MakeMaker? *and* Module::Build modules to your homedir with CPAN or What does "PREFIX not supported" mean? or How do I install to my home directory? or Installing Catalyst as someone other than root

In .bashrc:

  export PATH=$HOME/local/bin:$HOME/local/script:$PATH
  perlversion=`perl -v | grep 'built for' | awk '{print $4}' | sed -e 's/v//;'`
  export PERL5LIB=$HOME/local/share/perl/$perlversion:$HOME/local/lib/perl/$perlversion:$HOME/local/lib:$PERL5LIB

In .cpan/CPAN/MyConfig.pm (configure first then add this):

  'make_install_arg' => qq[SITEPREFIX=$ENV{HOME}/local],
  'makepl_arg' => qq[INSTALLDIRS=site install_base=$ENV{HOME}/local],

Now you should be able to simple CPAN on everything you need ...

See also Catalyst Advent - Day 10 - Catalyst on shared hosting


Getting the most from Class::DBI::Loader

Here are some tips on how I like to use Class::DBI::Loader, which makes it a lot easier to deal with if you are using it a lot.

First, I generally decide on a "super-namespace" to refer to the database itself, in most cases if the application is called MyApp, then I use MyApp::DB to refer to the database, and MyApp::DB::* to refer to the tables. This way you can load all the database information in one shot with use MyApp::DB;.

MyApp::DB usually starts something like this:

# Jason Kohles &lt;email@jasonkohles.com&gt;
# I generally use PostgreSQL for the database backend, some of this will probably need
# tweaking if you use something else
package MyApp::DB;
use DateTime::Format::Pg;
use DateTime::Format::Strptime;
use Class::DBI::Loader;
use DateTime; 
our $VERSION = (qw'$Rev: 15 $')[1];
our $ID = '$Id: DB.pm 15 2005-09-17 17:12:41Z jason $';

my $loader;
BEGIN {
    # I like to put the dsn/user information here, rather than in MyApp->config,
    # to make it easy to use this class from outside of Catalyst as well.  Generally
    # I like to use it for all database access, to make sure things like triggers and
    # constraints in the Class::DBI code are always honored.
    $loader = Class::DBI::Loader->new(
        dsn                 => 'DBI:Pg:dbname=MyAppDB',
        user                => 'dbuser',
        #debug              => 1,
        password            => '',
        namespace           => __PACKAGE__, # the name of this class determines the namespace
        relationships       => 1,
        left_base_classes   => [qw(Class::DBI::Sweet)], # Sweet!
        additional_classes  => [qw(
            Class::DBI::AbstractSearch Class::DBI::Plugin::AbstractCount
            Class::DBI::Plugin::Pager Class::DBI::MyFromForm
            Class::DBI::AsForm Class::DBI::Plugin::Type
        )],
    );

    # This sets the default output format for displaying dates
    my $format = DateTime::Format::Strptime->new(
        pattern => '%a %b %d, %Y  %I:%M%P %Z',
    );

    # loop through all of the classes that were just setup
    foreach my $class ($loader->classes) {
        # This section will attempt to load a perl module with the same name
        # as the class that was just autogenerated by Class::DBI::Loader, so
        # if you want to add code to any of the classes that it creates, you
        # just create them as regular perl modules in a directory where they
        # will be found by the use call.
        eval "use $class";
        if($@) { if($@ !~ /^Can't locate /) { die $@ } }

        # This loops through the columns in the database, looking for ones that
        # either begin or end with 'date' or 'time', or where the column_type call
        # returns '95' (PostgreSQL timestamp column), and sets those fields up to
        # be DateTime objects with appropriate inflate/deflate actions.
        $class->has_a(
            $_ => 'DateTime',
            inflate => sub {
                my $dt = DateTime::Format::Pg->parse_datetime(shift());
                $dt->set_formatter($format);
                return $dt;
            },
            deflate => sub {
                DateTime::Format::Pg->format_datetime(shift());
            },
        ) for grep {
            /_(time|date)$/ ||
            /^(date|time)_/ ||
            $class->column_type($_) =~ /^95$/
        } $class->columns;
    }
}

# return the loader if needed
sub loader { return $loader }

# if you really, really, need a direct database handle, generally I try to avoid this
sub dbh { return ($loader->classes)[0]->db_Main }

1;

-- Notes: Nov 08 2005

Minor issue, but unlike using a common base class without CDBI::Loader, CDBI::Loader (via Ima::DBI) creates a db_Main method in each class. This can cause Ima::DBI to call prepare_cached() when not needed. DBI will return the same database handle, but any options that were changed on the handle will get reset to their defaults. This can show up during a transaction when updating more than one table. That is, do_transaction sets AutoCommit? off on one $dbh, but then when updating another table Ima::DBI thinks $dbh is undefined and calls prepare_cached() which turns AutoCommit? back on.

BTW -- I use loader a bit differently than above. Instead I put the call to loader into a new() method which accepts connection parameters. That makes it easy to use it outside of Catalyst.

So then in catalyst:

    package App::M::CDBI;
    use strict;
    use MyDB;
    MyDB->new( App->config->{database} );
    1;

Getting the most from DBIx::Class::Loader

(From: blblack@gmail.com) - This is the base model class I created for using my PostgreSQL database from Catalyst using DBIx::Class instead of Class::DBI, which was influenced by and based on the example above. It does *not* derive from Catalyst::Model::DBIC, so it should be usable from outside Catalyst as well, but I don't use it that way at the moment, YMMV. Note that by convention in my database all timestamps are stored as SQL INTEGER types with a column name suffix of _stamp, and in my model I'm converting these stamps to and from DateTime objects for use in my Controller code. Also note the explicit disconnect of the database handle used to load the classes at the bottom. This prevents otherwise nefarious issues if you preload your Catalyst under mod_perl at startup (pre-fork) time.

package MyApp::M::MyDB;

use strict;

use DBIx::Class::Loader;
use DateTime;

my $loader = DBIx::Class::Loader->new(
    dsn           => 'dbi:Pg:dbname=mydb',
    user          => 'postgres',
    password      => '',
    options       => { AutoCommit => 1 },
    relationships => 1,
    namespace     => __PACKAGE__,
);

foreach my $class ($loader->classes) {

    # Make sure the class loads correctly
    eval " use $class; ";
    if($@) { if($@ !~ /^Can't locate /) { die $@ } }

    # Add automatic conversion to/from perl DateTime
    # objects for all _stamp columns in the database,
    # which are unix epoch timestamp integers.
    $class->inflate_column(
        $_, {
            inflate => sub { DateTime->from_epoch( epoch => $_[0] ) },
            deflate => sub { $_[0]->epoch },
        }
    ) for grep { /_stamp$/ } $class->columns;
}

($loader->classes)[0]->storage->dbh->disconnect;

1;

Character set issues with Catalyst::View::TT

Your browser seems to be ignoring the character set you set in <meta>? Apache configs don't help either? Using Catalyst::View::TT?

There's a line in Catalyst/View/TT.pm

sub process {

    ...

    unless ( $c->response->content_type ) {
        $c->response->content_type('text/html; charset=utf-8');
    }

    ...

}

You can set the charset appropriately with the same function call. I have

sub begin : Global {
    my ($self, $c) = @_;
    $c->res->headers->content_type( 'text/html; charset=iso-8859-1' );
    ...

in MyApp.pm


Error handling

You want to collect errors using the built-in error() method, like this:

$c->error('Something bad happened');

but you want to handle them by yourself, instead of seeing Catalyst's debug screen. The solution is to clear the error list in MyApp?'s end() method:

$c->clear_errors;

Apache2 performance is bad under load

I recently had an issue after installing a Catalyst app under Apache2 on Linux (prefork mpm).

Before going to production, I did some stress tests:

ab -n 100 -c 10 http://myapp.example.com/myapp/anypage

The performance was *very* bad, and swap file utilization was very high. It turned out I just forgot to preload the application:

# Important: don't forget the following line!
PerlModule MyApp
<Location /myapp>
  SetHandler perl-script
  PerlResponseHandler MyApp
</Location>

To read more about why module preloading is crucial, see: Practical mod_perl - Section 10.1.3. "Preloading Perl Modules at Server Startup" http://modperlbook.org/html/ch10_01.html

See also: "ab - Apache HTTP server benchmarking tool" http://httpd.apache.org/docs/2.0/programs/ab.html


The application home directory is not properly detected

If you start coding a Catalyst app from scratch (e.g. you create the skeleton by hand) you could find that the application home directory is not detected properly. For example you could have:

Found home "/path/to/MyApp/lib/MyApp"

instead of

Found home "/path/to/MyApp"

This causes strange bugs like YAML complaining about missing config.yml even if it is in the correct place. Before looking for errors in the code, make sure to put at least an empty Makefile.PL or Build.PL in the application top directory. Those file are automatically created if you use the helper scripts and are used to identify the application home.


Non-root (shared hosting) apache configuration

If you don't have permission to set Alias or ScriptAlias in your .htaccess file, create a folder named myapp in your html root, then create a new myapp/.htaccess file containing:

RewriteEngine on
RewriteRule (.*) /cgi-bin/myapp.cgi/$1

This will do an internal redirect to your /cgi-bin/myapp.cgi script.

See also Catalyst Advent - Day 10 - Catalyst on shared hosting


Can't locate object method "storage" via package ...

If you began developing with an older version of Catalyst, you might encounter this error when using Catalyst::Model::DBIC. The problem is stemming from SQL::Abstract::Limit. Catalyst supposedly requires version 0.101 or higher of SQL::Abstract::Limit, but it will not (as of 5.61) tell you if you have an older version of SQL::Abstract::Limit. By updating this manually, the error should be resolved.


CGI.pm fails with "client aborted" error on file uploads

A Catalyst app running under Apache as a CGI script began to fail to accept file uploads. Every upload resulted in a 30-second pause, then an error in the Apache error log from CGI.pm claiming that the client aborted the upload.

It turned out that because I had -Debug enabled, there was a deadlock between my application and Apache. Apache writes the whole request to the application, and then reads its STDOUT and STDERR. If the application produces output on STDOUT or STDERR before it finishes reading the entire request, that is buffered in the pipe between the CGI and Apache. When that buffer fills, the app will block on that write. But Apache will never read it until the app reads all of the request.

Disabling -Debug cleared it up temporarily. Moving to mod_perl allowed me to re-enable -Debug, since STDOUT and STDERR are connected directly to their destinations and not to a pipe to Apache.


"Can't locate method 'handler'..."-like error messages when trying to run a Catalyst application under apache2/mod_perl2 (win32)

It is important that you have at least mod_perl 2.0.1. Modperl 2.0.0 seems to confuse Catalyst about what apache engine to load.


Installing Catalyst under Gentoo

Installation of Catalyst on Gentoo Linux is explained in the Catalyst Framework Howtohttp://gentoo-wiki.com/HOWTO_Catalyst_Framework. There are also pointers to the unofficial ebuilds, which are available for Catalyst and for many plugins as well.


Configure first, setup later

The CPAN and CPANPLUS methods of installing Catalyst come with some hazards, and depending upon your platform Some plugins handle their default settings different. Because of this you really have to make sure you call PACKAGE->config BEFORE PACKAGE->setup() Otherwise you can get very hard to debug problems.


Quickly installing Catalyst

Installing Catalyst can be a less-than-trivial process on some platforms, particularly ones where the native perl installation has little in the way of common CPAN modules already installed. Chris Laco has created CatInABox? to attempt to ease the pain of an initial Catalyst install. Thanks Chris!


Deploying using PAR + Perl Packager on shared host with old perl (5.6.1) and no root

You will need a setup similar to the one you are deploying on (similar LIBC, similar versions of all shared libraries like libpg for instance) that you can install or at least get the application running on. Then starts the trial and error. PAR has functionality for scanning for dependencies, but it misses out on most run-time loading. What I did was put in simple "use Module::XYZ;" in my base class for each module that was having trouble (use Class::DBI::Iterator; is needed if your using CDBI).

To create your first PAR

perl Makefile.PL
make catalyst_par

If this errors out with a make Error 2, then your app failed to start. what you can do to debug this is 1: strace make catalyst_par or 2: perl blib/par.pl (which it leaves around when it failes).

one of those should give you a clue to whats wrong (trouble with connecting to the database and other runtime problems).

once you get a MyApp?.par file in the root of your distribution, its time to embed the perl interpreter with it, to make a standalone executable:

pp -o myapp MyApp.par

now its time to test it localy:

./myapp myapp_server.pl

if this works, copy the myapp binary to the host of choice, and try the same there. If that works, you are close to something. Sadly this is where things get rough around the edges.

I needed to deploy using fastcgi, but the engine packaged with par by default is CGI, so I had to change the Makefile, near the end of it, to say ENGINE => 'FastCGI' instead of the default ENGINE => 'CGI', run trough the whole process again, and voila, now I have my app deployed on a shared host without root access, with perl 5.6.1 and without installing a single module trough cpan on the shared host.

I also made a really simple "init.d" style script to stop and start my fastcgi-daemon:

#!/bin/sh
ROOT=/www/dagbladet/home/andreasm
PIDFILE=/tmp/mpe.pid

ACTION=$1
cd $ROOT

case $ACTION in
        start)
                ./mpe mpe_fastcgi.pl -l :8080 -p /tmp/mpe.pid -n 5 -d
        ;;
        stop)
                PID=`cat $PIDFILE`
                kill $PID
        ;;
        *)
                echo "Usage: $! start|stop"
        ;;
esac

Apache/mod_perl segfaults when loading Catalyst app using MySQL

This was driving me nuts - I had an app running on top of postgresql. I duplicated it, altered the duplicate to run on MySQL instead - it worked fine under the in-built server. I copied my original app's apache config and tailored it to the new application - restart apache - segmentation fault. Bugger.

So I thought, this must be something stupid i've done...spent hours trawling through the code and the apache config looking for my error. nothing.

Eventually mst suggested it might be something to do with PHP, so I commented out the LoadModule? for php in apache and lo and behold, no segfault. It would seem, as mst suspected, that on my server DBD::mysql and PHP aren't compiled against the same libmysql. With CGI this doesn't seem to matter, but with Catalyst under mod_perl it does, causing Apache to blow up.

I'm not 100% sure yet how to solve this. If someone has an answer feel free to let me know (guy@alchemy.com.au) and I'll update this entry.


Installation under windows with Active State and PPM

See http://catalyst.infogami.com/katalytes/cat_on_windows


Apache 1/2 + mod_fastcgi / external server - missing preceeding slash

I set out on a mission to deploy my catalyst app, using Apache and mod_fastcgi. I followed the catalyst docs to the dot, but ended up with an annoying problem. It seemed the catalyst app received a request for 'user/login' when the request was in fact for '/user/login'. After hours of headache I found the solution to be adding a \/ in the alias directive:

FastCgiExternalServer /tmp/myapp -socket /tmp/myapp.socket
<VirtualHost *>
        ServerAdmin admin@myapp.org
        ServerName myapp.org
        DocumentRoot /srv/www/myapp.org/apacheroot

        LogLevel warn
        ErrorLog /srv/www/myapp.org/apachelog/error.log
        CustomLog /srv/www/myapp.org/apachelog/access.log combined

        Alias /static /srv/www/myapp.org/root/static
        Alias / /tmp/myapp/\/
</VirtualHost>

-- trym


Exempting specific pages from cacheing.

what i want to do is cache all pages for an app except of pages ending in /debug and /expire

this is accomplished with

# MyApp/lib/MyApp.pm
__PACKAGE__->config->{page_cache} = {
   expires => 60,
   auto_cache => [
       '(?!.*/(debug|expire)\z).*',
   ],  # cache everything but requests ending in /(debug|expire)
};

while in MyApp?/lib/MyApp/Controller/Root.pm i got

sub begin : Private {
   my ( $self, $c ) = @_;

   if ($c->config->{page_cache} && $c->req->path =~ m#(.*)/expire$#) {
       $c->clear_cached_page( '/' . $1 );
   }
}

so actually you can call clear_cached_page from the cached controller in contrast to what the manpage says:

      clear_cached_page

      [...]                                                 For obvious
      reasons, this must be called from a different controller than the
      cached controller. You may for example wish to build an admin
      page that lets you clear page caches.

kudos for mauke at #perl for help with the negative lookahead :D

--bruha

Logins with case insensitive usernames

f you need to have case insensitive logins but do not dare to overwrite internal methods, there still is a way.

First, make sure you have unique case insensitive usernames (the below example is for Pg)

CREATE UNIQUE INDEX unique_username_ignorecase ON users USING btree (UPPER((username)::text))

It will not be possible to add that index if you already have two or more non-unique users (I hope).

Then, you can use this:

my $user = $c->req->param('user');

if ( my $icu = $c->model('DB::Users')
                 ->single({ 'UPPER(username) => \"=UPPER('$user')) ) {

        $user = $icu->username;
}

if( $c->login($user, $c->req->param('password')) ) {
        ... hello friend
} else {
        ... please go away
}