=head1 Name
Bigtop::Docs::Tutorial - a simple case study of building a web app with bigtop
=head1 Note on What to Read
This document explains how to build an app of moderate complexity by typing
in a bigtop file. Since it was written, tentmaker has come along. It
is a browser delivered editor for bigtop files, see Bigtop::Docs::TentTut
for details.
If you need a simpler example than the one shown here, consider
the one table address book example in Gantry::Docs::Tutorial.
There are even more examples in the Gantry book. Visit http://usegantry.org
to purchase the book and to download the sample code from it.
=head1 Driving Idea
Many applications are mostly data managers. That is, they are
really intermediaries between users and various tables in a database.
A bigtop file is meant to be a single place to describe all (or practically
all) facits of the data in an application. This includes at least:
=over 4
=item *
The name and special features of each controller.
=item *
The name of each table in the database.
=item *
A description of each column (field) in each table in the database.
This includes at least:
=over 4
=item *
its name and SQL type
=item *
the label the user sees for it when it appears on the screen
=item *
what type of html form element the user uses to enter or update it
=item *
how the data is validated and filtered on its way into and out of the database
(filtering not yet supported)
=item *
which table the field refers to if it is a foreign key
=item *
etc.
=back
=back
All of these things, and more, are described in a Bigtop file. That file
can be given to bigtop to build the application. Once it is built, it
can be safely rebuilt so that only the generated bits are changed (this is
accomplished by maintaining a clean separation between generated and hand
edited files, and by config options in the bigtop file).
Notice that nothing in the above has committed you or me to any particular
web application framework, data modeling scheme, templating system,
or web server. Bigtop is neutral (think big tent), at least for
Perl apps delivered via the web.
=head1 A Working Example
Here I will present a small, but useable application. It's purpose is
to teach you the syntax of the bigtop.
=head2 The Assumptions I'm About to Make
In order to explain the bigtop syntax, I'm going to exhibit a
particular example. It will use the following:
=over 4
=item *
the Apache web server running mod_perl 1.3
=item *
the Postgres database
=item *
the DBIx::Class data modeler
=item *
the Template Toolkit templating system
=item *
the Gantry web application framework
=back
I made these choices just for concreteness. Bigtop already supports CGI,
Gantry's hand written data modeler, and Class::DBI::Sweet. Eventually, I
hope to make the scheme work with other choices like Catalyst, Mason, etc.
Whether that happens or not depends on my spare time or (more likely) on
people interested in using Bigtop with those modules.
=head2 The Example
Just to have an example, suppose you've taken up free-lancing (consulting,
writing, etc.). You've chosen to represent yourself under a business
name or two. The customers are lining up for your services, so its time
to tame your billing process.
In what follows, I will present a bigtop file a few lines at a time
with comments interspersed. The full file is examples/billing.bigtop
in the Bigtop distribution. Please consult it when you need to
see how all the pieces look together.
Caveat: I made some of design decisions for this app just to showcase
Bigtop and how it interacts with Gantry. But, a colleague actually uses
a very similar app for his side consulting business. Reality is just
around the corner.
=head2 The Data Model
There are five tables in my version of the billing app:
=over 4
=item customers
people paying me
=item my_companies
the names I use when doing business
=item invoices
bills I email to the customers
=item line_items
the tasks that are listed on the invoices
=item status
a code for whether the invoice is under construction, mailed, or payed
=back
There is a nice picture of the data model in billing_model.png in the
distribution's examples directory. If you're viewing with a browser this
might help:
=for html
We've actually done the hardest part by constructing the data model. The
rest is really just typing it in.
=head2 Initial Creation
When starting an app from scratch one might use a tool like h2xs to build
diretory structures and standard files (like Changes). This is a good
idea with bigtop too. We could just run bigtop to make a default app:
bigtop --new Billing
Instead we can make it do a lot of initial work for us if we give some
information about our data model. That could be as simple as listing the
tables:
bigtop -n Billing customers my_companies \
invoices line_items status
Or, we could go further and tell it about the foreign key relationships
between the tables:
bigtop -n Billing 'customers<-invoices
invoices->status invoices->my_companies
line_items->customers line_items->invoices'
Pick one of the above versions to start following the discussion below.
Since this document started before the -n flag to arguments, the
billing.bigtop file it describes will differ from yours if you use
either of the -n versions above. But, the differences are mainly in the
order in which things appear. For instance, when I typed in the file
I chose to keep all the tables together and they come first. Then
the controllers follow as a group. With -n each table/controller
pair will appear in the order of first appearance of the table name
on the command line. There are other similar differences, but all of
them are only cosmetic. All the commands are the same and have the
same effect, even if the order differs slightly.
Any of the bigtop -n options above will make the Billing subdirectory of
the current directory. In it you will find many files including: Build.PL,
Changes, MANIFEST, MANIFEST.SKIP, README plus directories: docs, html, lib,
and t. The directories will have lots of goodies, including:
docs/schema.sqlite for building a quick test database with sqlite,
html/genwrapper.tt a template toolkit wrapper providing a tan theme for the
app, a set of models for the tables, and a set of controllers for those
tables. Finally, the docs directory will have billing.bigtop, which will
edit until it describes our app more correctly.
All the tables will have the same default columns: id, ident, description,
created, and modified. The ones on the tail end of a foreign key
will also have a column for that foreign key.
At this point, we could create the database and start the app. But since
the tables don't store our actual data, it is better to wait.
We will now begin editing docs/billing.bigtop. After we edit it,
we will regenerate the app. When you run bigtop to do that, you should
be in the directory where the Changes file lives. (If you aren't, it will
not build for you without strong insistence.)
Before looking at the generated bigtop file (which is a little over
200 lines), let's consider the basic structure of bigtop files. They all
look like this:
config {
engine CGI;
template_engine TT;
# ...
}
app Billing {
# ...
}
Note that there are two top level blocks: config and app. Let's consider
these in separate sections. First, note that any line whose first
non-whitespace character is a pound sign is ignored as a comment.
=head2 Configuration
At the top of each bigtop file is a config section. In it, we list a few
properties of the app and what pieces bigtop should generate for us.
With the --new option to bigtop the config section looks like this:
config {
engine CGI;
template_engine TT;
Init Std {}
SQL SQLite {}
SQL Postgres {}
SQL MySQL {}
CGI Gantry { with_server 1; gen_root 1; flex_db 1; }
Control Gantry { dbix 1; }
Model GantryDBIxClass {}
SiteLook GantryDefault {}
}
First there are two statements. They request the engine and template
engine which will service the app. This one will run under CGI (for
the time being) and will rely on Template Toolkit for output formatting.
When we are ready to move to mod_perl, we can simply change the engine,
add a backend (HttpdConf Gantry), regenerate, and be ready to go.
After the statements is a list of the backends which built things for us.
Take this line for example:
Init Std {}
This specifies that the Bigtop::Backend::Init::Std module should be loaded
whenever bigtop runs. We could load the backend, but ask it not to do
anything by changing this statement to:
Init Std { no_gen 1; }
Initially (as it were) Init Std builds lots of things, like Changes, README,
and Build.PL. After that, all it ever does is update MANIFST. By setting
no_gen, you ask it not to do that; presumably because you have a more
manual scheme in mind for MANIFEST maintenance. Every backend can be turned
off in this way. Turning off a backend, instead of removing it, allows
it to register its keywords. If your source file uses keywords specific
to a backend, failing to include that backend will lead to parse errors.
Turning off the backend lets people know you used it in the past and lets
the Parser know you want it to recognize the backend's keywords.
In addition to Init, bigtop put seven other backends to work for us.
Three of these build schema files for databases, making it easier to
move between them. For instance, it is often convenient to start work
on an app with a sqlite database, then migrate to one of the others
for deployment.
The other four backends each do a different thing (as indicated by their
different types). CGI Gantry makes a CGI script ready for immediate
deployment to our cgi-bin directory. Since with_server has a true
value, it also makes a handy stand alone server we can use during initial
development. The gen_root statement adds a config param called root
pointing to the html subdirectory (more on root and other config params
later).
Control Gantry makes the controllers in the lib subdirectory.
Model GantryDBIxClass makes models for use with the DBIx::Class ORM.
Finally, SiteLook GantryDefault makes a TT wrapper.
So, from these examples, we've learned that the name of the backend has
a type and an implementation name. These can be anything, so long as
there is a Bigtop::Backend::Type::ImplName in the C<@INC> path. So, we
have asked for Bigtop::Backend::Init::Std, Bigtop::Backend::SQL::SQLite,
etc. There is some advice on building your own backend in
Bigtop::Docs::Modules.
=head3 Order is (somewhat) important
Note that generation usually happens in the order listed. So, if you type:
bigtop billing.bigtop all
The generation order will be Init, SQL, ...
This seldom matters much, except that Init has to come first during
creation, since it builds the directories.
This principle is true for the other sections of the bigtop file. So, if
order is important in the output, use that order in the bigtop file.
=head2 app section
The app section has this form:
app Billing {
#...
}
Everything shown below is inside the app block, but we will update
things according to our billing app spec. The name is the package
name of the base controller and is also a prefix for all other modules.
config {
dbconn `dbi:SQLite:dbname=app.db` => no_accessor;
template_wrapper `genwrapper.tt` => no_accessor;
}
You can include any config parameters you like in the app level config block.
The backends will move them into the proper place for app configuration.
For example, the CGI Gantry backend will put them into a hash in the
CGI script (and stand alone server). The HttpdConf Gantry backend would
instead make them PerlSetVars. Later we will see how to use Gantry::Conf,
then the Config General backend will put them into a flat file readable
by Config::General.
Normally, an accessor will be generated for each one in the base controller
module. But, if you mark them no_accessor, as bigtop did above,
that accessor will not be generated. Presumbably your framework will provide
accessors for them in that case, as Gantry does for the ones shown.
These config variables are of two types: database and app navigation.
Within Gantry, database connection is handled with dbconn, dbuser, and
dbpass. dbconn is a full DBI connection string. (SQLite doesn't use
dbuser or dbpass, so bigtop doesn't generate them.) Note that if dbuser
or dbpass include any characters Perl wouldn't like in a variable name,
you must backquote the string, as in:
dbpass `s!m0n` => no_accessor;
The other parameter is the name of the generated TT wrapper. It lives
in the html directory.
=head3 Encoding the data model
After the environmental setup is described, there are two remaining
pieces: the data model and the controllers which manage it.
We'll start with the data model.
For this app there is one basic block that describe that model: table.
Table blocks take this form:
table name { ... }
Inside the braces, you can include either statements or field blocks. There
are three legal statements. One of them was supplied for us: foreign_display.
It comes after the gernated fields.
table my_companies {
# ... generated fields
foriegn_display `%ident`;
We want to change this from C<`%ident`> to C<`%name`>.
This controls what is displayed when other tables refer to this one. In
this case, they will see the name of the company. If there were people
in a table, foreign_display might be C<`%last, %first`>. So, percent followed
by a column name will be replaced with the value of that column for each
row. Note that the percent sign in the value requires us to surround
all foreign_display values with backquotes.
We'll use the data statement in the status table below. The third statement
is useful only if you want to use Postgres sequences.
The rest of the customers table is a set of field blocks. Like other blocks,
field blocks have this form:
field name { ... }
Inside is a list of statements. Some of those shown below
are specific to Gantry, in particular, many depend on its default
templates.
field id {
is int4, primary_key, auto;
}
All fields must have an C statement. This fully specifies their
SQL properties. Mostly, you want to list a valid SQL type (where valid
means your database understands it). You can provide a single keyword,
a list of keywords, a backquoted string, or a comma separated combination
of those. Back quoted strings are taken literally.
You may use the C type even if your database doesn't understand
it. Then the backend for your database will convert it into a reasonable
integer type. You may always choose to be explicit instead of relying
on that magic.
You should use the bare C as one attribute of the id column.
This not only generates 'PRIMARY KEY' in the SQL output, but
marks the column as primary for the ORM.
There is a special keyword you may use to fill in the primary key by
default: C. The SQL for this request varies by database, but
all three supported databases work properly.
Note that primary_key and auto must be bare (not inside
quotes). Remember: backquoted strings are taken literally.
Since ids rarely appear on screen, they usually only have an is statement.
Other fields are shown to the user and thus have other statements.
When bigtop makes a new app from a list of tables, it puts five columns in
each table: id, ident, description, created, and modified. If you included
ASCII art table relationships, other fields will be generated to represent
them. We talked about id above, and we don't need to change it. The other
fields are too generic for our billing app. So, we finally have a bit of
work to do (other than changing the foreign_display).
Here is what the generated ident field block looks like:
field ident {
is varchar;
label Ident;
html_form_type text;
}
The description field looks just like it with the name changed. The created
and modified fields are more like this:
field created {
is date;
}
These are meant to be managed internally, not to be shown to the user.
For now, we'll keep the created and modified fields (even though they
aren't in the model as we originally drew it). But, we won't do anything
with them, so if you don't like them, delete them.
We need to change the name of the ident field to name and its label
to Name. We wanted a description field, so we'll keep it. But we don't want
to require it, so add an html_form_optional statement:
field name {
is varchar;
label Name;
html_form_type text;
}
field description {
is varchar;
label Description;
html_form_type text;
html_form_optional 1;
}
I've also added some whitespace to make things look a little better. Any
whitespace between the tokens is ignored by bigtop. In fact, if you
use tentmaker, all whitespace is normalized. So, my extra internal
spacing would be removed on any trip through tentmaker.
To finish the my_companies table, we need to add the rest of the contact
information to it. Simply copy the address field, paste it six times
and change the field names and labels, until your customers table has these:
field city {
is varchar;
label City;
html_form_type text;
}
field state {
is varchar;
label State;
html_form_type text;
}
field zip {
is varchar;
label Zip;
html_form_type text;
}
field contact_name {
is varchar;
label `Contact Name`;
html_form_type text;
}
field contact_email {
is varchar;
label `Contact Email`;
html_form_type text;
}
field contact_phone {
is varchar;
label `Contact Phone`;
html_form_type text;
}
}
While there are many other statements you could use, the four shown
here are the most common.
=over 4
=item is
SQL type information
=item label
what the user sees on the screen as the column label in HTML tables where
the field appears and next to the entry elements where values for it are
entered.
=item html_form_type
the input type for this field when it appears in an HTML form.
Current Gantry templates only understand select, text, textarea, and
display.
=item html_form_optional
the field is not required even when it appears on a user input/update form.
This is not the same as C which indicates that the
data modeler should not retrieve the value until you call an accessor for the
field.
=back
The other tables are similar.
table customers {
foreign_display `%name`;
field id { is int4, primary_key, assign_by_sequence; }
field name {
is varchar;
label Name;
html_form_type text;
}
field address {
is varchar;
label Address;
html_form_type text;
}
field city {
is varchar;
label City;
html_form_type text;
}
field state {
is varchar;
label State;
html_form_type text;
}
field zip {
is varchar;
label Zip;
html_form_type text;
}
field description {
is varchar;
label Description;
html_form_type text;
html_form_optional 1;
}
field contact_name {
is varchar;
label `Contact Name`;
html_form_type text;
html_form_optional 1;
}
field contact_email {
is varchar;
label `Contact Email`;
html_form_type text;
html_form_optional 1;
}
field contact_phone {
is varchar;
label `Contact Phone`;
html_form_type text;
html_form_optional 1;
}
}
Easy date handling is a key feature of Bigtop and Gantry. The line item
table has a due date for the task it describes:
sequence line_items_seq {}
table line_items {
sequence line_items_seq;
foreign_display `%name`;
field id { is int4, primary_key, assign_by_sequence; }
field due_date {
is date;
label `Due Date`;
date_select_text Select;
html_form_type text;
}
The date_select_text will appear as an HTML href link next to the entry
field for the date. If the user clicks the link, a popup will display
an intuitive calendar. If the user clicks a date on the popup calendar,
the text input box will be populated with that date.
Of course, this behavior is driven by the controller.
For example, see the LineItem controller below.
field name {
is varchar;
label Name;
html_form_type text;
}
field my_companies {
is int4;
label `My Company`;
refers_to my_companies;
html_form_type select;
}
Because we used the -> notation when running bigtop originally, the
foreign key fields are already present. But here is how to add one
manually: use the refers_to statement to say which table it points to.
Note that it must point to the primary key of the other table and we
generally assume that the key will be the unique id column.
Bigtop::Backend::SQL::Postgres does not currently (nor is it ever likely
to) generate genuine SQL foreign keys. If you want foreign
keys, and your database supports them, consider implementing your own SQL
backend to generate the SQL code for them.
Note that when we used bigtop, it chose the foreign key column names
to match the foreign table names exactly. Our original data model
diagram used slightly different names. If you want the bigtop file
to match the picture, change the names of the foreign key fields.
The Gantry html_form_type for foreign key columns is C