Press Releases with Mason
Last month, we took an initial look at Mason, a template system that sits on top of mod_perl and allows us to create fast-executing dynamic web sites built out of small components.
This month, we will look at a simple application built in Mason—a system to display the latest press releases on a corporate site. Of course, such a system could be tailored in a number of ways, including an on-line newspaper or other publication in which information changes on a regular basis. In creating this small site, we will see some of the steps involved in working with Mason.
The core element of our news system will be a relational database. I will use MySQL in these examples, although any relational database system should work fine.
I created a new MySQL database called “atfnews” on my MySQL server and assigned privileges so that the user atfnews can connect using the password “atfpass”. I then created the following two tables:
CREATE TABLE Categories ( category_id SMALLINT UNSIGNED AUTO_INCREMENT, category_name VARCHAR(25) NOT NULL, PRIMARY KEY(category_id), UNIQUE(category_name) ); CREATE TABLE Articles ( article_id MEDIUMINT UNSIGNED AUTO_INCREMENT, category_id SMALLINT UNSIGNED NOT NULL, posting_date TIMESTAMP NOT NULL, headline VARCHAR(30) NOT NULL, body TEXT NOT NULL, PRIMARY KEY(article_id), UNIQUE(category_id, headline) );
As you can probably tell from their names, the Categories table contains a list of category ID numbers and names. The Articles table contains several more pieces of information, including an article ID, the category ID into which an article should be placed, the date and time at which the article was posted, the article's headline and its body. We ensure no two articles in a given category have the same headline with a UNIQUE clause at the end of our CREATE TABLE statement.
The posting_date column takes advantage of MySQL's TIMESTAMP data type. This type automatically inserts the time and date of the latest INSERT or UPDATE to a given row. In this way, we can easily determine when news stories were added to the database, without having to enter or keep track of the information ourselves.
In order for our news system to work, we will need to create at least two different sets of components. One set will allow users to enter news items into the database (i.e., perform INSERTs), and the second will make it possible to retrieve items from the database (i.e., perform SELECTs). In a production setting, we would probably want to restrict posting access to a selected number of users. This would be possible with a standard .htaccess file, which allows users to restrict access to individual files or directories, or with a more sophisticated system that stores user information in a database.
One of Mason's strong points is its use of components. Components are actually Perl subroutines, cleverly disguised in the form of HTML files with some Perl thrown in. (Mason's parser performs the underlying magic that turns components into subroutines.) This structure means that repeated functionality can be packaged into one component, then invoked from within other components.
For example, Listing 1 contains a component called “database-connect.comp”. This component returns a value, rather than producing HTML output. Its purpose is to connect to a database server and return a database handle, typically called $dbh. By centralizing this connection code, we can easily move our site from one server to another, changing only the relevant $host, $user, $password and $database variables as necessary.
Once database-connect.comp has been configured, any component on our system can receive a valid database handle with the following code:
<%init> my $dbh = $m->comp(database-connect.comp); </%init>
The above code takes advantage of Mason's object-oriented interface, using the predefined $m object to invoke another component.
By placing the assignment inside of <%init>, we ensure that the component will connect to the database before anything else occurs within the component. However, this also means we are creating a new lexical variable ($dbh) with each invocation of the component.
It would be slightly more elegant to perform the above assignment within a <%once> section, creating $dbh a single time and keeping the value around. However, <%once> sections are executed outside of the Mason component context, meaning they cannot invoke methods on $m. Moreover, <%once> sections are invoked before new Apache child processes are created, which a $dbh object might not like. Thus, it is common to define $dbh in a <%once> section, but to perform the assignment in <%init>:
<%once> my $dbh; </%once> <%init> $dbh = $m->comp(database-connect.comp); </%init>
The plain-vanilla mason.pl (or “handler.pl”, as the Mason documentation describes it) configuration file that comes with the Mason distribution is almost good enough for this system to work. We need to load only Apache::DBI, a wrapper module that works with DBI within the mod_perl environment, ensuring that database connections are created and dropped only as necessary.
In order to load Apache::DBI, we need to put a use Apache::DBI statement in mason.pl, which is loaded with a PerlRequire statement in httpd.conf. In order to save some memory, we insert a PerlModule Apache::DBI line into httpd.conf. This ensures the module is loaded into memory before Apache splits into numerous child processes. The module might still require a fair amount of memory, but at least that memory will be shared among all Apache processes rather than requiring each one to have its own copy.