Day 7 - Rapid CRUD with Catalyst
Many of the other Advent Calendar entries this year will focus on similar technologies used in much more glamorous ways. This entry is down-and-dirty form-based CRUD. You know, the kind that likely still pays your bills.
Catalyst has its origins in Maypole, which was a CRUD-oriented web application framework. See http://www.perl.com/pub/a/2004/04/15/maypole.html for example. While Catalyst is much more flexible now and not tied to CRUD in particular, CRUD remains one of the most frequently implemented features. And now, it can be one of the easiest to implement as well.
Impatient? Here's the code.
We need a Rose::DB subclass to manage the database connections.
For this tutorial we'll create a test database with SQLite called
Read the Rose::DB::Tutorial documentation when you get to the point
of connecting to your existing database(s).
Create a subclass of Rose::DB called MyDB.pm:
package MyDB; use base qw( Rose::DB ); MyDB->register_db( database => 'advent_example.db', driver => 'sqlite' ); 1;
Now we need a script to create the Catalyst application. The first thing we'll do is create a simple database schema using MyDB. Then, we'll bootstrap a Catalyst application called MyApp. And finally, fill out MyApp with full-featured CRUD functionality.
#!/usr/bin/perl use MyDB; use Rose::DBx::Garden::Catalyst; my $db = MyDB->new(); $db->dbh->do("CREATE TABLE foo ( id integer primary key, name varchar(128) );"); system("catalyst.pl MyApp") and die $!; my $garden = Rose::DBx::Garden::Catalyst->new( db => $db, catalyst_prefix => 'MyApp', find_schemas => 0, ); $garden->plant('MyApp/lib');
Now start up your app and point your browser at http://localhost:3000/rdgc/.
NOTE: In order for your app to find MyDB.pm you need to make sure it is in @INC, so start up your app from the current dir like:
or explicitly include the current dir like:
cd MyApp && perl -I.. script/myapp_server.pl
In a real-world application you might instead modify @INC with a
use lib in
I know. CRUD is so, like, 1995.
CRUD (Create, Read, Update, Delete) web applications are just about the oldest, most ubiquitous type of web app around. That makes them boring, right? Or at the very least, a wheel that has been invented so many times that it isn't worth your time. And yet, so many projects involve at least some level of CRUD functionality. We're not rid of CRUD yet, even if the glamour is long faded.
At least, that's what I thought recently when I set about building a CRUD app for what seemed like the 1000th time. This time, I thought, is the last time I write all that foundational code by hand. If building CRUD apps is like shoveling snow, I wanted to buy a snowblower.
I was charged with designing both the database schema and the web application that would front it. I knew several things:
- My preferred ORM and form-manager packages were Rose::DB::Object and Rose::HTML::Objects.
- The database schema was going to require several iterations of "write, get customer feedback, re-write."
- Any serious web app is always going to have project-specific data validation requirements ("business logic"), so any kind of abstraction layer I tried to write for "generic CRUD" was going to have to be easy to extend and customize later.
- I wanted to bootstrap a web application that allowed me to do basic CRUD on the dummy data with which I was developing the schema, but that would form the legitimate basis of a real application later. This wasn't a "quick disposable prototype" (though those have their purpose). This was a "how much code can I generate automatically without it being either (a) too project-specific or (b) too simple to use later."
After all, this wasn't going to be the last CRUD app I ever wrote, so I wanted to build something that I could use again for the next one. And the next, and ... well, you get the picture.
Planting a Garden
The result is Rose::DBx::Garden::Catalyst. RDGC is a code and template generator similar in spirit to Catalyst::Example::InstantCRUD, but it uses the Rose packages instead of DBIC and HTML::Widget. And it incorporates the sexy AJAXy goodness of the YUI toolkit.
RDGC tries to follow best practices for the Rose packages and for Catalyst. The basic philosophy is a single base class from which each part of the application (RDBO, RHTMLO, Controller, Model) could inherit, and a single base template for each view. RDGC also sticks all its code in its own namespace, so you can use it to generate CRUD code for your already-existing Catalyst apps.
Most importantly, RDGC lays out its files in a structure that makes it straightforward to extend the basic CRUD features and add project-specific logic later. For more on the file/class structure RDGC uses, see the CataystX::CRUD::Tutorial.
The CatalystX::CRUD project's ambition is to implement a single, simple API for a variety of ORM and form packages. The idea is that it ought to be possible to exchange RDBO for DBIC, or RHTMLO for HTML::FormFu or Form::Processor, and make minimal changes to your Catalyst code. The RDBO Model and RHTMLO Controller are the most mature parts of CatalystX::CRUD project, since they were first incarnated as Catalyst::Model::RDBO and Catalyst::Controller::Rose and went through real-world production testing before morphing into CatalystX::CRUD. But there's a DBIC Model currently in testing and plans for other form manager implementations.
CRUD is Dead. Long Live CRUD!
It's important to emphasize that RDGC is not intended to produce a final product. It can certainly be used as a rapid prototyping tool. But hopefully it also lays out a structure that encourages Catalyst and Rose best practices and makes it easier to extend and customize your application.
Point RDGC at an existing database and try it out. If nothing else, the YUI data table feature is a handy way to quickly search, browse and page through your existing data.
Peter Karman <karman at cpan dot org>