Name
Gantry::Docs::FAQ - Frequently asked questions and answers about gantry
Intro
This document is like a FAQ, except no one asked us these questions. We made the list to collect our knowledge in one place before we forgot it.
The questions are:
Install
Coding
Deployment
Appearance
Authentication
Scripts
Install
How do I install Gantry?
Method 1 (preferred): install via CPAN
> perl -MCPAN -e shell
cpan> install Gantry
Method 2: Download the source (http://www.usegantry.org), extract it, change to that directory and run the following commands:
> perl Build.PL
> ./Build test
> ./Build install
Method 3: Check out Gantry from version control and then install
> mkdir gantry
> cd gantry
> svn checkout http://svn.usegantry.org/repo/gantry/trunk/ .
> perl Build.PL
> ./Build test
> ./Build install
Note that there are prerequisites. Further note that during testing some tests are skipped with warnings if you don't have suggested prerequisites. Installing the modules mentioned there is helpful.
How do I install Gantry on Shared Hosting?
Download the source, extract it, change to that directory and run the following commands:
[ Note on Installing Dependancies:
If your hosting provider will not install the module Dependancies you can just install them from source. You probably will not have access to install into the system perl directories. ]
> perl Build.PL
> ./Build test
> ./Build install --install_base=<path to your local perl modules>
Coding
What is the smallest app I could write with Gantry?
To see small Gantry apps consult Gantry::Docs::QuickStart and/or Gantry::Docs::Tutorial.
How do I turn off templating to dump raw text?
You can choose the formatting of your choice as long as your choice is Template Toolkit or no formatting. To choose the latter, in the use statement that initially loads Gantry (or more likely an app which uses it) specify -TemplateEngine=Default:
use Gantry qw{ -TemplateEngine=Default }
Alternatively, you can turn off formatting for one page request in the relavent handler:
$self->template_disable( 1 );
Even after you turn automated TT off, you can still use it:
$self->stash->view->template( 'your_temp_template.tt' );
my $partial_output = $self->do_process() || '';
I turned off templating must I hand write all HTML?
Use the Gantry::Utils::HTML
module to help you. This will often result in cleaner code with fewer tag problems. See its docs for details of the helper methods it provides.
How do I use gantry's AutoCRUD?
Once you have a database and a supported model for it, you can use Gantry's AutoCRUD. First,
use Gantry::Plugins::AutoCRUD;
This is more a mixin than a plugin. It exports do_add, do_edit, do_delete, and form_name into your module. The form_name method merely returns the text 'form.tt' which is the name of the default template for add/edit forms. If you want a different template, don't import form_name, instead implement your own form_name method.
For the exported AutoCRUD methods to work you must implement three methods:
- get_model_name
-
Returns the package name of the model for your table.
- text_descr
-
Returns a one- or two-word description of the items in the table.
- form
-
Receives the site object and, during editing, the row object the user is modifying. Returns a hash reference suitable for direct use as the view.form of form.tt. See the comments in form.tt for a list of what you can put in the hash. (If your template is different consult it.)
See the docs in Gantry::Plugins::AutoCRUD for additional optional methods. The other methods give you control over things like where users are taken when they cancel a request.
If DBIx::Class is your object relational mapper (ORM), you need to implement get_orm_helper and have it return 'Gantry::Plugins::AutoCRUDHelper::DBIxClass'.
If your ORM does not conform to the either the Class::DBI API or the to DBIx::Class API, you need to read the AutoCRUDHelpers section of the docs in Gantry::Plugins::AutoCRUD and provide your on helper module.
What about retrieval?
Your model should provide convenient retrieval methods. If you use DBIx::Class, you can inherit from Gantry::Utils::DBIxClass instead of from DBIx::Class to gain a couple of useful retrieval methods.
Gantry's Gantry::Utils::CDBI inherits from CDBI::Sweet, so it responds to all the standard Class::DBI methods (with the Sweet additions).
Both Gantry::Utils::DBIxClass and Gantry::Utils::CDBI provide get_listing which is suitable for most main listings.
What if AutoCRUD won't work for me?
Gantry::Plugins::AutoCRUD is somewhat rigid, but not as rigid as the above example makes it seem. For instance, suppose the above won't work because you need to specify a creation time for every add. AutoCRUD provides hooks.
These are the hooks:
- add_pre_action
-
Called after form data validation and before the new row is inserted into the database. It receives a hash reference of form parameters. This is a good time to add things like creation dates:
sub add_pre_action { my ( $params ) = @_; $params->{ created } = 'now'; }
- add_post_action
-
Called after row insertion. It receives the new row. This is a good time to log changes or send email to interested (or uninterested) parties.
- edit_pre_action
-
Called after form data validation and before changing the data in the database. It receives the row object which is about to be changed and a hash reference of form parameters which are about to become the new values for that row. Make your changes in the params hash. For example, you could insert a new key:
sub edit_pre_action { my ( $self, $row, $params_hash_ref ) = @_; $params_hash_ref{ modified } = 'now'; }
- edit_post_action
-
Called after changing the data in the database with the row object as amended. Do whatever you like. A common pattern is to pair pre and post actions. For instance, we have an application which sends email only if the status field of a row has changed. Its edit hook methods look roughly like this:
sub old_status { # an accessor my ( $self, $old_status ) = @_; if ( $old_status ) { $self->{__OLD_STATUS__} = $old_status; } return $self->{__OLD_STATUS__}; } sub edit_pre_action { # sets the old_status attribute my ( $self, $row, $params_ref ) = @_; $self->old_status( $row->status ); } sub edit_post_action { # sends mail if the status changed my ( $self, $row ) = @_; my $old_status = $self->old_status(); my $new_status = $row->status; if ( $old_status != $new_status ) { $self->send_status_mail( $row ); } }
- delete_pre_action
-
Called after user confirmation and immediately before the doomed row is removed from the database. It receives the row object which is about to meet its maker. This is a good place to log its demise in some other table or die if something has gone awry.
- delete_post_action
-
Called immediately after the row has been removed from the database (commit has already been called). It receives the id number of the deceased. This might be a good place to log the event.
- get_relocation
-
Called with the current action (add, edit, or delete) and either submit or cancel (depending on which button the user pressed). Return the url where you want the user to be redirected.
- get_submit_loc
-
Ignored if get_relocation is defined.
Called with the current action (add, edit, or delete) and the string 'submit'. Return the url where you want the user to be redirected.
- get_cancel_loc
-
Ignored if get_relocation is defined.
Called with the current action (add, edit, or delete) and the string 'cancel'. Return the url where you want the user to be redirected.
You may implement as many of these methods as you like in your controller. Any that are not implemented are simply not called.
What if AutoCRUD really won't work for me?
While the answer to the last question shows a certain amount of flexibility in the AutoCRUD scheme, sometimes it just isn't enough. If you want control, but don't want to worry with the basics of displaying the form, validating results, etc. Gantry::Plugins::CRUD is for you.
To have a concrete example, suppose my controller posts comments on a blog entry, but only if the user is logged in.
Unlike its AutoCRUD counterpart, Gantry::Plugins::CRUD does not export anything. Instead it is an object oriented helper. Here's how it works. First, use it:
use Gantry::Plugins::CRUD;
Then make an instance of it, being explicit about what it should do when:
my $comment_crud = Gantry::Plugins::CRUD->new(
add_action => \&add_comment,
edit_action => \&edit_comment,
delete_action => \&delete_comment,
form => \&my_form,
template => 'comment_form.tt',
text_descr => 'comment',
);
There are other keys you may use -- see the perldoc for Gantry::Plugins::CRUD for details.
We'll look at the actions in some detail below. First, let's examine the other hash keys here.
The form method must return a single value (usually a hash reference), which will go to the template that shows the form on the screen. The text_descr is used when asking the user to confirm a deletion.
All that remains is to implement your own do_ methods to catch the CRUD requests for your controller.
do_add {
my ( $self, $blog_id ) = @_;
unless ( $self->is_logged_in ) {
die 'You must log in to post a comment'
}
$comment_crud->add( $self, { blog_id = $blog_id } );
}
If the security check passes, the CRUD plugin's add method will take care of showing the comment form and validating the data on it. Once it is satisfied that the comment is valid, it will call add_comment (or whatever you registered as the add_action when you called the constructor). Here's an example:
sub add_comment {
my ( $self, $params, $data ) = @_;
$params->{ blog_id } = $data->{ blog_id };
$params->{ user_id } = $self->user_id;
$params->{ created } = 'now';
$params->{ body } = $self->sanitize_body( $params->{ body } );
my $new_row = Model::comments->create( $params );
$new_row->dbi_commit;
$self->send_spam( $data->{ blog_id }, $new_row );
}
You are completely responsible for updating the database in the add_action. A good model helps with this.
The other actions work similarly. Note that there is no need to be completely honest with the names. It would a good use of Gantry::Plugins::CRUD to implement do_delete so that it marked rows as invisible rather than deleting them. That wouldn't be possible with AutoCRUD.
As a final note, it is not necessary to define all the options for a Gantry::Plugins::CRUD object. It is fine to have only delete_action and the keys it needs. You may also have different objects for add, edit, and/or delete. This gives an easy way for add and edit to use different forms, for example.
Can I use AutoCRUD and/or CRUD if I wrote my own models?
This is really two questions. First, 'Can I use AutoCRUD with hand written models?' The answer is: Yes, so long as one of the following is true of your ORM. (1) it responds to dbi_commit, create and retrieve calls, the objects returned by retrieve respond to delete, and -- when your form is used for editing -- it expects a row object returned by your retrieve. (2) you implement a helper similar to Gantry::Plugins::AutoCRUDHelper::DBIxClass.
Second, 'Can I use CRUD if I wrote my own models?' The answer is: Yes. For CRUD above restrictions don't apply since it works even if there is no model.
How do I control error page appearance?
When something in Gantry dies, the main handler traps the error and calls custom_error on the site object to generate the error page. Simply implement your own custom_error to change how the error output appears to your users. Note that it is often useful to change from a developer-friendly version to a user-friendly version as you move to production.
The custom_error method is invoked on the site object. It receives an array of error output lines.
How can I let my users pick dates easily?
Date entry is controlled by form.tt. To make it work you need to do four things, the first two of them in them in your form method:
Name your form by including this key in the returned hash:
name => 'your_name',
Add a javascript key to the returned hash:
javascript => $self->calendar_month_js( 'your_name' ),
your_name must match the name from step 1.
Add a date_select_text key to the hash of each date field:
{ date_select_text => 'Popup Calendar', # ... }
Add a uses element for Gantry::Plugins::Calendar to the controller:
controller Name { uses Gantry::Plugins::Calendar; }
See Bigtop::Docs::Tutorial for how to make these steps happen from bigtop files.
How can I use DBIx::Class with Gantry?
There are several things you need to do to use DBIx::Class effectively with Gantry:
Implement your DBIx::Class::Schema class like this:
package YourApp::Model; use strict; use warnings; use base 'DBIx::Class::Schema'; use Gantry::Utils::ModelHelper qw( db_Main ); __PACKAGE__->load_classes( qw/ list tables here / ); sub gen_db_Main { my $class = shift; return sub { return $class->db_Main(); }; } sub get_db_options { return { AutoCommit => 1 }; } 1;
By using Gantry::Utils::ModelHelper as shown, you can rely on the existing Gantry database connection scheme (keep reading for details or see Gantry::Docs::DBConn for complete info).
Impelement one module for each table which inherits from Gantry::Utils::DBIxClass. These will be very similar to modules which inherit from DBIx::Class, but will inherit to useful methods for Gantry CRUD schemes (get_listing and get_form_selections). Example:
package YourApp::Model::table_name; use strict; use warnings; use base 'Gantry::Utils::DBIxClass'; __PACKAGE__->load_components( qw/ Core / ); __PACKAGE__->table( 'table_name' ); __PACKAGE__->add_columns( qw/ id other columns / ); __PACKAGE__->set_primary_key( 'id' ); sub get_foreign_display_fields { return [ qw( other ) ]; } sub get_foreign_tables { return qw(); } sub foreign_display { my $self = shift; my $other = $self->name(); return "$other"; } sub table_name { return 'table_name'; } 1;
These models will be ready for use with Gantry CRUD schemes.
Add to your controller:
use YourApp::Model; use YourApp::Model::table_name qw( $TABLE_NAME ); sub schema_base_class { return 'YourApp::Model'; } use Gantry::Plugins::DBIxClassConn qw( get_schema );
This mixes
get_schema
into your site object and gives you a shortcut to your model's extra methods through <$TABLE_NAME>.Use AutoCRUD directly (follow its docs).
Use your models in your controller:
my $schema = $self->get_schema(); my @rows = $TABLE_NAME->get_listing( { schema => $schema } );
Use
$schema
according to the docs in DBIx::Class.
Bigtop can generate all of this for you.
How can I use Gantry's native models?
Perhaps a better answer a question: 'Why would you want to?' If you have a good answer for that, the short answer to the original question is: use Bigtop. Better yet, use the tentmaker and select Model Gantry on the 'Backends' tab. Gantry's models require a lot of code. They were designed to be generated.
Deployment
How do I deploy a gantry app under mod_perl?
There are three steps to placing most gantry apps under mod_perl.
Include PerlSetVar statements for configuration information in the root Location block for the app:
<Location /> PerlSetVar dbconn dbi:Pg:dbname=mydb PerlSetVar dbuser unknown PerlSetVar dbpass none_of_your_business PerlSetVar root /home/you/templates:/home/gantry/root SetHandler perl-script PerlHandler Apps::Malcolm </Location>
You probably need more set vars, but that should be enough to show you where they go.
Modify your httpd.conf to include a Location for each controller in your app:
<Location /sub> SetHandler perl-script PerlHandler App::Base::Module::Sub </Location> <Location /sub2> SetHandler perl-script PerlHandler App::Base::Module::SubOther </Location> <Location /sub/system> SetHandler perl-script PerlHandler App::Base::Module::Sub::System </Location>
Use gantry in a way that loads one of the mod_perl engines. There are multiple ways to do this. All of them involve a use statement like this:
use Gantry qw{ -Engine=MP13 -TemplateEngine=TT };
(Replace MP13 with MP20 if you use mod_perl 2.)
This statement can go in a <Perl> block above the root location. It could also go in your startup.pl. Finally, you could put it in the base module's .pm file. It makes no real difference on a single environment system. The first two methods are better if you might also deploy the app in a different environment (since then the app would never know which environment it was in, and so no code change would be needed to move environments).
How do I deploy a gantry app under CGI?
Create an executable file in a cgi-bin directory like this one:
#!/usr/bin/perl
use strict;
use App::Base::Module qw{ -Engine=CGI -TemplateEngine=TT };
my $cgi = Gantry::Engine::CGI->new(
{
locations => {
'/' => 'App::Base::Module',
'/sub' => 'App::Base::Module::Sub',
'/sub2' => 'App::Base::Module::SubOther',
'/sub/system' => 'App::Base::Module::Sub::System',
}
config {
dbconn => 'dbi:Pg:dbname=mydb',
dbuser => 'unknown',
dbpass => 'none_of_your_business',
root => '/home/you/templates:/home/gantry/root',
}
}
);
$cgi->dispatch();
Adjust the config parameters to fit your app. Use as many locations as you like.
How do I deploy a gantry app under FastCGI?
FastCGI requires only a couple of slight changes to the above CGI script.
Wrap the dispatch statement like this:
use FCGI;
my $request = FCGI::Request();
while ( $request->Accept() >= 0 ) {
$cgi->dispatch();
}
What is Gantry::Conf?
Gantry::Conf provides a complete configuration scheme for both web and traditional programs. It allows you to share configuration between your web app and its cron scripts. It lets you run multiple instances of the same app in the same apache instance with separate configurations. It also lets you share configuration between apps even if they run on different servers (by allowing for http or https access to a single conf file).
See Gantry::Conf::Tutorial for how to set up your conf files among other details.
How do I use Gantry::Conf under mod_perl?
To make Gantry::Conf work, you must tell it which instance you need to configure. In mod_perl do this by setting this variable at the root location of your applications:
PerlSetVar GantryConfInstance your_instance_name
If you are not using the default /etc/gantry.conf for Gantry::Conf's configuration, set one additional variable:
PerlSetVar GantryConfFile /full/path/to/your/conf.file
How do I use Gantry::Conf under CGI/FastCGI?
To make Gantry::Conf work, you must tell it which instance you need to configure. In CGI and FastCGI do this by setting this by setting this key in the config hash passed to the Gantry::Engine::CGI constructor:
GantryConfInstance => 'your_instance_name'
If you are not using the default /etc/gantry.conf for Gantry::Conf's configuration, set one additional variable:
GantryConfFile => '/full/path/to/your/conf.file'
Appearance
Where does Gantry look for style sheets, templates, etc.?
The answer depends somewhat on how you deploy the application. Under CGI it looks in the config section of the hash you passed to Gantry::Engine::CGI->new. Under mod_perl it looks for PerlSetVar statements.
In both cases the names it looks for are the same. Here are the ones Gantry.pm understands (these are highly integrated into the standard templates):
- content_type
-
Used to generate the Content-type: header. Defaults to text/html.
- template
-
The default template to use for page rendering. Usually overridden by a do_* method.
- template_default
-
(Ignored if template above is defined and no do_* method places a template in the stash.)
The default template to use for page rendering.
- template_wrapper
-
The Template Toolkit WRAPPER for the site.
- template_disable
-
Set this flag to a true value to turn off template processing.
- root
-
The template root (in the TT sense) for the app. Gantry will add the installed location of the default templates which ship with it to the end of your root path.
- css_root
-
The absolute path to the css directory on your disk.
- img_root
-
The absolute path to the image directory on your disk.
- tmp_root
-
The absolute path to a tmp directory on your disk.
- app_rootp
-
The root uri for your application. Defaults to '' which is equivalent to '/'.
- css_rootp
-
The root uri for css files. Try '/css'. Then place your css files in the css subdirectory of your document root.
- img_rootp
-
The root uri for image files.
- tmp_rootp
-
The root uri for tmp files.
- editor_rootp
-
The root uri of the html editor for the application (so users can enter html without worry on your part or theirs).
Authentication
What tables do I need in my database for authentication?
Gantry uses four tables for its authentication system. You can either put these into your app's database, or store them in another database. This allows you to share auth between multiple apps.
The schema for the tables is in the SCHEMA FOR AUTH TABLES section of Gantry::Control#SCHEMA%20FOR%20AUTH%20TABLES.
How do I add authentication to my app?
Gantry provides integrated support for Apache's basic authentication. First, you need to have a database for authentication or put the auth tables into your app's database (see the previous question).
Then, you need to add the following Apache directives to the base location for your app (or to the location at which auth should begin):
<Location /myloc>
AuthType Basic
AuthName "Your Auth Realm Name"
PerlSetVar auth_dbconn "dbi:Pg:dbname=your_auth_db"
PerlSetVar auth_dbuser apache
PerlSetVar auth_dbpass super_secret
PerlAuthenHandler Gantry::Control::C::Access
PerlAuthenHandler Gantry::Control::C::AuthenRegular
PerlAuthzHandler Gantry::Control::C::AuthzRegular
</Location>
You need to set auth_dbconn, auth_dbuser, and auth_dbpass even if your auth tables are in your app's database.
Finally, add directives to the proper locations:
require group NAME
require valid-user
If you want to limit access by IP, set this perl var for the location:
PerlSetVar auth_allow_ips "172.168.2.41,172.168.2.182'
Note: if you are doing anything important, you should run it through ssh to keep the sniffers from seeing your passwords.
How do I add gantry authentication to my non-gantry app?
You don't need a gantry app to use gantry's auth modules. Simply follow the instructions in the answer to the previous question.
How do I add gantry authentication to my static page location?
You don't even need an app to use gantry's auth modules. Just do the same thing you would do if you had an app.
How can I share my authentication database across multiple apps?
Two or more apps can share one auth database by setting their auth_dbconn to the same connection string. This does require a bit of coordination in the auth_groups and auth_group_members tables.
Scripts
How can my cron (and other) scripts use an app's models?
If you use DBIx::Class, do its normal thing:
my $schema = Your::Schema->connect(
'dbi:Pg:dbname=yourdb;host=127.0.0.1', 'user', 'pass'
);
But note that Bigtop generates the schema as Model.pm, even though it is not a model and no models inherit from it.
If you use any ORM which uses the DBConn scheme (see Gantry::Docs::DBConn), your script should use the provided helper:
use Gantry::Utils::Helper::Script;
Gantry::Utils::Helper::Script->set_conn_info(
{
dbconn => 'dbi:Pg:dbname=yourdb;host=127.0.0.1',
dbuser => 'auser',
dbpass => 'secret',
}
)
Change the methods as needed (to reflect your database names, user, and password or to allow the main package to fill in the values from command line args or config file info). This works for all models which inherit from Gantry::Utils::CDBI.
If any of your models inherit from Gantry::Utils::AuthCDBI, then you must include auth_conn_info.
Gantry::Utils::Helper::Script->auth_conn_info(
{
auth_dbconn => 'dbi:Pg:dbname=yourauthdb;host=127.0.0.1',
auth_dbuser => 'auser',
auth_dbpass => 'super_secret',
}
);
Note that some models which inherit from Gantry::Utils::CDBI might have foreign keys pointing to other models which inherit from Gantry::Utils::AuthCDBI. In that case, even though you don't directly see the need for auth, you do in fact need it.
Summary
If you have other questions, send them in to the gantry mailing list -- details on joining can be found on http://usegantry.org. Maybe some day this will be a genuine FAQ.
Author
Phil Crow <philcrow2000@yahoo.com>
Tim Keefer <tkeefer@gmail.com>
Copyright and License
Copyright (c) 2006, Phil Crow.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.