Building Applications With Gantry

All the prowess of Perl, plus a powerful application framework

Gantry ( "http://www.usegantry.org" class=
) is a "i">Perl- powered Web application framework. Started as a
proprietary toolkit some ten years ago, Gantry has evolved through
three major revisions. In mid-2005, Gantry was released as open
source software under the same license as Perl itself, and
maintenance and enhancement continue to this day. Like many other
frameworks, Gantry implements the classic Model-View-Controller
(MVC) pattern. However, Gantry allows you to use as much as or as
little of the pattern as you’d like. If you code in Perl and
want to both jumpstart and simplify your next Web application,
Gantry can give you a boost.

Let’s build an application of moderate complexity from
scratch using Gantry and its code generation framework,
Bigtop. To keep things simple, this example
uses Gantry’s built-in Web server. Migration to "i">mod_perl is straightforward, and the Gantry
documentation provides examples of deployments.

The first step is to install the Gantry
and Bigtop packages from the "i">CPAN. If necessary, install "c">HTML::Prototype, "c">File::Copy::Recursive, and "c">Data::FormValidator first and then install Gantry and

$ sudo cpan
cpan> install HTML::Prototype 
cpan> install File::Copy::Recursive
cpan> install Data::FormValidator
cpan> install Gantry
cpan> install Bigtop

When you install Gantry and "c">Bigtop, provide valid pathnames (or accept the defaults)
and answer “yes” to all of the prompts to install of
the components and supplemental files. In addition to code and
templates, the install adds two utilities, "i">bigtop and tentmaker to

Welcome to Consultants, Inc.

Suppose you are a high-powered consultant whose billing process
is collapsing under the weight of an ever-expanding client list.
You need a new invoicing system desperately, and it must be simple
to use and easy to maintain.

After some thought, you define four tables for your new
invoicing system: the company table
maintains the names you do business as; "i">customer lists the people who hire you and often owe you
money; invoice enumerates bills from a
company to a customer; and task represents a
line item on an invoice.

Turning this data model into a working Gantry application is
extremely simple. Bigtop generates (and re-generates) "i">SQL and Perl code given a description of a table. To
create company and "i">customer, run:

$ bigtop –n Billing \
    ’company(name,phone,email) \

The –n Billing argument generates a
brand new application named “Billing.”
(“Billing” is also the name of the base Controller
module.) The remainder of the command-line creates the SQL for the
two tables, with three columns each. By default, columns are
strings and are required. (Later, you’ll see how to make a
column optional and how to change its type.)

As soon as bigtop finishes, you have a
fresh Gantry application ready to track your companies and
customers. bigtop even emits instructions to
help you start your application from the command-line:

I have generated your ’Billing’ application.  To run the application:

    cd Billing
    sqlite app.db < docs/schema.sqlite
    ./app.server [ port ]

The app.server runs on port 8080 by default.

Once the app.server starts, it will print a list of the urls it can serve.
Point your browser to one of those and enjoy.

If you prefer to run the app with PostgreSQL or MySQL type one of these:

    bigtop --pg_help
    bigtop --mysql_help

Follow the instructions by changing to the new "i">Billing directory. If you have "i">sqlite (version 3 or higher) in
your path, Bigtop has already created a database for you.
Otherwise, you’ll need to build a database before you can use
your new application.

Bigtop supports three databases: SQLite,
and PostgreSQL, but it uses
SQLite by default. If you don’t want to use SQLite, build
your database with one of the other SQL files found in the
docs subdirectory of the build directory.
(Gantry has no database engine limitations, except that you must
have the proper DBD module for your database installed. However,
bigtop can only generate SQL code for the
three engines listed.)

Once the database is in place, you may start the application
using the command-line Web server. If you’re using SQLite,
this reduces to typing:

$ ./app.server

Or, if you’re using one of the other databases, add
command-line flags for the database type, database name, username,
and password. For instance you might need something like this to
connect to PostgreSQL:

$ ./app.server –d Pg –n billing –u payable –p ’$ecr3t’

Of course, payable must be a valid
PostgreSQL user on your system with the password shown.
billing should exist, initialized with the
SQL found in doc/scheme.postgres.

When you start the application, the server prints a list of URLs
it can serve:

Available urls:




The …/company and "i">…/customer URLs lead to basic CRUD ( "i">Create, Retrieve, Update, and "i">Delete) management of those tables. The base URL
displays a page of links to the other URLs. For example, if you
visit http://localhost:8080/company, you will see something like
Figure One.

The selection box on the left allows you to quickly change to
other table home pages during development. If you click the
Add link, the application displays the form
in Figure Two.

To add a company, enter a name, phone number, and email address,
since all of the fields are (currently) required, and click

The customer table pages look exactly the same (since the tables
have the same columns), except that customer replaces name on the
window labels.

Revisions (Without Writing Code)

A key feature of Bigtop is that you can regenerate the
application at any point to improve the model or the basic behavior
of controllers. For instance, the Billing application currently has
only two tables. Let’s add the other two tables and make sure
the new tables have good foreign keys.

The invoice table should look like

number      varchar
description varchar or text (to show the customer)
sent        date
paid        date
status      int4
notes       varchar or text (for internal use)
company     (foreign key to company table)
customer    (foreign key to customer table)

And the task table should have these

name            varchar
description     varchar or text (to show the customer)
due_date        date
hours           int4
charge_per_hour int4
notes           varchar or text (for internal use)
invoice         (foreign key to invoice table)

To add these to the model, make sure you are in the build
directory (the directory where you started "i">app.server) and type the command shown in "i">Figure Three.

Extending the initial model to
include new tables and foreign keys
$ bigtop –a docs/billing.bigtop ’invoice->company invoice->customer task->invoice \
    invoice(number,description,+sent:date,+paid:date,status:int4,+notes) \

The –a option takes the name of a
Bigtop file to add to. The rest of the command-line is an ASCII art
description of how to augment the Bigtop file. (The argument must
be quoted because it contains the shell special character
>. Spaces are significant, too —
don’t use them in the middle of column definitions.)

There are three new Bigtop features demonstrated in the ASCII

*The dash arrows, "c">->, indicate a foreign key from the tail of the arrow
pointing to the head. So, for example, the "i">task table has a foreign key to the "i">invoice table.

*The other new features are
found in the column definitions. A leading plus sign on the column
name makes it optional on HTML forms. A trailing colon introduces
the SQL type. (int4 and "c">date are special to Bigtop. The various SQL engine
backends convert those two types into appropriate integer and date
types for each database.) If your database has custom types, feel
free to use any of those.

You can create all of the tables at once. The previous example
shows that you can augment an existing Bigtop file.

Before restarting the application, add the new tables to the
database. The simplest way is to cut-and-paste the SQL found in
docs/schema.sqlite to the "i">sqlite3 prompt. Paste the create table blocks for the
invoice and task

Using tentmaker to Edit Bigtop Files

The next step in construction is to create the status codes
found in the invoice table. Creating a table
for the codes is one approach, but a simpler technique is to use an
HTML selection list to control what values
are permitted in the table. (You could solve this in other ways,
but most of those are specific to particular databases. Let’s
try and keep the application as portable as possible.)

Bigtop is something of a programming language and "i">docs/billing.bigtop is the source code. You can edit
Bigtop files with a text editor (and you will), but another program
named tentmaker is specifically designed to
edit Bigtop files. tentmaker is itself a
Gantry application, so you interact with it through your Web
browser. (Use Safari or "i">Firefox. Internet Explorer
isn’t supported.)

To start tentmaker, move to your build
directory and type:

$ tentmaker docs/billing.bigtop

Then connect to your server at "i">http://localhost:8080/. (Yes, you can change the port
number if you want to run your application and "i">tentmaker at the same time. See the documentation for
tentmaker to learn how.) You should see
something like Figure Four.

To edit the status field, click on the” App Body” tab, and
scroll down to the table named” invoice.” You should see something
like Figure Five.

Click Edit and scroll down until you see
Figure Six. From the select list, choose”
Status” to edit that field.

There are many options for the field, but you can focus on only
two. First, because you want users to pick values for the status
from a drop down selection list, choose "c">Select in the pull down for” html_form_type,” as shown
in Figure Seven.

Next, you must supply the select list labels and the
corresponding database values. Look for” html_form” options and
fill it in to match Figure Eight. So, the
statuses (expressed as tuples here) are (Working on it, 1),
(Billed, 2), and (Paid, 3).

Users will see the labels on the screen, while the database
stores the values. (There is even a generated function in the Model
that any Controller can use to convert the database value into its
label string.)

When you have all the columns in place, go back to the
tentmaker” Bigtop Config” tab and press
Save As. The correct file name should
already be in the box. Then press Stop
and confirm your decision in the pop-up dialogue box.
Now, regenerate the application and restart it with:

$ bigtop docs/billing.bigtop all
$ ./app.server

If you don’t use –n or
–a with bigtop,
you must specify what to build. Usually, you want to build
everything, so add all to the end of the
command. If, however, you want only new Models, you can type
bigtop docs/billing.bigtop Model, but
that’s a rare need.

Customizing the Application

There are two things left to make the billing application
useful. First, it would be nice to see and be able to edit the
tasks for just one invoice. Second, it would be great to have
actual invoices that can be sent to clients.

To tie tasks to the invoice page, you need just a little more
time in tentmaker:

$ tentmaker docs/billing.bigtop

(When you restart tentmaker in the same
browser session, you must request a page refresh. This avoids very
annoying caching problems.)

To start, change the Invoice controller.
On the” App Body” tab, click Edit for the
Invoice controller. Again, this reveals a lot of information about
the controller, but you can restrict your changes to the listing of
all invoices. Scroll down to do_main(), the
method that produces that list. Click Edit
to reveal information about the method, then scroll down until you
see the row options, as shown in Figure

The row options control what users can do with each row in the
underlying table. The standard options for editing and deleting a
row are already in place. Below those, add "c">Tasks as the Label and "c">“/task/main/$id” as the Location.

A URL labeled” Tasks” will now appear on each line of the
Invoice page. A click takes the user to the "c">Task controller’s do_main()
method, passing the ID of the invoice (the row). The "c">Task controller uses the ID to find only those tasks
associated with the invoice.

The next step is to modify the Task
controller to expect hits like this one. Remaining in "i">tentmaker, edit its do_main()
method. For” limit_by” enter invoice
the name of the table whose row IDs will be coming into this

Save the file, stop tentmaker, and
regenerate the application again with bigtop
docs/billing.bigtop all
. Finally, restart "i">app.server. If you haven’t added data, now is the
time. Enter at least one company and customer, and add a couple of
invoices, with at least one task in each. Finally, go to the
Invoice page and click’ Tasks’ for one of the

Show Me the Money

The last feature to add is to create bills suitable for mailing
to clients. Here, let’s make a simple text invoice. (But you
could generate a PDF or other output just as easily. Look in the
directory examples/Billing-Finished of the
Bigtop CPAN distribution for a PDF version and some other
extensions that make this app more usable.)

Go back to tentmaker and add” Make
Invoice” below” Tasks” for do_main() of the
Invoice controller. There is no need to
specify a location, since the default works in this case.
Afterwards, save the result and regenerate. It’s time to
write some code.

If you restart app.server now and click
Make Invoice, you cause an error like the
one shown in Figure Ten.

Below the summary box you can also see lots of details about the
request Gantry was unable to process. The maroon box says that the
Billing::Invoice module, which lives at the
/invoice URL, does not have a method called
do_make_invoice(). This is precisely
correct, so let’s make that method.

The name of the method is formed from the row option label.
It’s converted to lowercase; spaces are replaced with
underscores; and the prefix do_ is
prepended. All URL linked methods in Gantry begin with "c">do_.

To create the method, edit "i">lib/Billing/Invoice.pm with your favorite text editor.
Near the top of that file you’ll see this line:

use Billing::Model::invoice qw( $INVOICE );

It brings in the Invoice Model and an all caps alias for it. You
need to use the Task Model in the same way, So add this under the
line above:

use Billing::Model::task qw( $TASK );

With easy access to the data model, you’re ready to add
the do_make_invoice method(). Some sample
code is shown in Listing One.

builds an invoice from tasks
01 #-----------------------------------------------------------------
02 # $self->do_make_invoice( $id )
03 #-----------------------------------------------------------------
04 sub do_make_invoice {
05     my ( $self, $id ) = @_;
07     # pull variables out of invoice row ready for here doc
08     my $invoice     = $INVOICE->gfind( $self, $id );
09     my $invoice_num = $invoice->number;
10     my $sent        = $invoice->sent;
11     my $description = $invoice->description || ’’;
13     $description    = "\n$description\n" if $description;
15     # my company data
16     my %corp_data;
18     foreach my $column qw( name street city state zip phone email ) {
19         $corp_data{ $column } = $invoice->company->$column();
20     }
22     # customer data
23     my %cust_data;
24     foreach my $column
25         qw( name street city state zip phone email )
26         {
27             $cust_data{ $column } = $invoice->customer->$column();
28         }
30     # tasks, pass the buck
31     my ( $task_output, $total ) = $self->_task_output( $id );
33     my $retval = << "EO_Invoice";
34     Billed By:
35     $corp_data{ name }
36     $corp_data{ street }
37     $corp_data{ city }, $corp_data{ state } $corp_data{ zip }
38     $corp_data{ phone }  $corp_data{ email }
40     Billed To:
41     $cust_data{ name }
42     $cust_data{ street }
43     $cust_data{ city }, $cust_data{ state } $cust_data{ zip }
44     $cust_data{ phone }  $cust_data{ email }
46     Invoice Number: $invoice_num Invoice Date: $sent $description
48     Date         Hours    Rate/hr    Total   Task
49     $task_output
50     _______________________________________________________________________
52     Total Amount Due: $total
54     Invoice due upon receipt.
55     EO_Invoice
57     $self->template_disable( 1 );              # turn off templating
58     $self->content_type( ’text/plain’ );
60     return $retval;
61 }

Gantry handlers are called as methods through the Gantry site
object. Since this one is a default row option handler, it also
receives the row id from the invoice table (see "i">Line 5). Step one is to pull that row from the table on
Line 8. Gantry models are based on
DBIx::Class, but have some added sugar for
common queries. Here, gfind() is called on
the invoice model through its all caps alias — that method
needs the Gantry object and the row id.

DBIx::Class makes an object for the
invoice row in the database, which is stored in $invoice. It
responds to accessors for each column in the underlying table.
Using those, you can easily pull the invoice number, date sent,
description, and so on from the invoice row object (see
Lines 9-11).

When there are foreign keys, the "c">DBIx::Class object has an accessor

named for the foreign key column. That accessor returns an
object for the row in the foreign table. The” foreign” object has
accessors for all the columns in that table. Using the object in
for loops on Lines
and Lines 22-28, you can pull
in all the data for the company (in "c">%corp_data) and customer (in "c">%cust_data).

The balance of the routine is a here document to produce the
text attachment for an email to the customer. At the bottom of the
method, templating is disabled (Line 57) and
the Content-type header of the response
(Line 58) is set to "c">text/plain, so the browser receives only the text of the
invoice without wrapping or styling.

The do_make_invoice() method also needs a
helper method to make the line items. Listing
is similar in spirit to the code of "i">Listing One, but works on the "i">task table:

A helper function to enumerate all of the
tasks associate with an invoice
sub _task_output {
    my ( $self, $id ) = @_;

    my @tasks = $TASK->gsearch( $self, { invoice => $id } );

    my @rows;
    my $total       = 0;
    my $space       = ’ ’;

    foreach my $task ( @tasks ) {
        my $row_amount = $task->hours() * $task->charge_per_hour();

        $total        += $row_amount;

        my $row_output = $task->due_date()        . $space x 4;
        $row_output   .= $task->hours()           . $space x 8;
        $row_output   .= $task->charge_per_hour() . $space x 9;
        $row_output   .= $row_amount              . $space x 10;
        $row_output   .= $task->name();

        push @rows, $row_output;

    my $task_output = join "\n", @rows;

    return $task_output, $total;

The only new thing is the gsearch() sugar
method. It works like gfind(), but allows
specific values for any columns. Actually, you can use the full
power of SQL::Abstract with "c">gsearch(). Here, it must only find all the tasks with a
given invoice ID.


Bigtop makes building and maintaining Gantry applications
straightforward, without limiting the framework’s inherent
flexibility. With it, you can gain a huge initial development push
once you have a preliminary data model. But you also have the power
to make large changes to most of the modules in an application via
regeneration, leaving only custom code for you to write and

By the time you read this, there should be a Gantry
book available from lulu.com, see the Gantry project site for a

Comments on "Building Applications With Gantry"

Nice post. I was checking constantly this weblog and I am inspired! Extremely useful info specially the closing part :) I deal with such information a lot. I used to be looking for this particular information for a long time. Thanks and good luck.


I was wondering if you ever considered changing the page layout of your blog? Its very well written; I love what youve got to say. But maybe you could a little more in the way of content so people could connect with it better. Youve got an awful lot of text for only having 1 or two images. Maybe you could space it out better?


Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>