Summary

In this article, we describe how to design and implement an online voting page with Mason, the powerful Perl based template management system for building dynamic web sites. We first design tables that store member information, voting results and etc. Then we implement some helper functions that manipulate the rows in these tables. These functions can be tested fully and independently outside Mason and Apache's environments. Next we write Mason powered components and HTML files that use these functions to generate the voting pages, process user input, display the results and etc.

Introduction

Sometimes you may need to create an online voting page for your group or mailing list. You may find some scripts from the Web. In this article, we not only provide source code but also describe in details how to design and implement a simple and secure voting page. In doing this, we hope it will be much easier for you to adapt the code to your requirements.

Nowadays email address is ubiquitous, so we can use it to identify our users. Many online groups or mailing lists allow you to export the member list from which you can build a member database easily.

When a user comes to your voting page and provides an email address, you can check your member database to see if the user exists in it or not. Because only the email address owner can read his/her email, you can generate a voting code based on it and send it to the associated email address so the user can use the voting code to authenticate himself/herself.

The voting code can be generated programmatically and doesn't incur any additional storage overhead. For example, for low security environment, you can just use as the voting code the first six characters of the MD5 of the user's email address and a secret that is known to yourself only.

With these ideas in mind, we can start designing the database tables. Then we can implement some functions that manipulate the data in these tables. These functions should be well tested outside Mason and Apache's environment. Only after these two steps are done, shall we proceed with design and implementation of the voting page which mostly glues together all the functions we implement earlier.

Table design

Table design is quite simple for the voting page. The first table stores the candidate's information. In addition to name and email address, we also need to assign each candidate a unique numeric ID so other tables can reference it easily. The second table stores all your group members' information. Similar to the candidate table, only name and email address are crucial. You also need to assign each of your members a unique numeric ID. The third table stores the voting results. It needs to have two fields minimally: Candidate ID and Voter ID.

To avoid spamming your members, you also need to have a request table that tracks when email containing the voting code is sent to a given email address. With this information, you can enforce how often you want to send the voting code to an email address, e.g., at most once every day. This table just needs to have three fields: The voter ID, the time the voting email is sent and the IP address that the request comes from.

If you are concerned that someone may do something mischievous, you can also add another table that tracks the voter's IP address. Later on you may detect something fishy if many votes come from the same IP address.

You can download the sample SQL file that creates the voting database and all the tables discussed earlier.

Implementation of supporting functions

After designing all the tables, we are ready to implement some supporting functions. We prefer this bottom-up approach so we can implement and test them fully before we actually build the pages. We can use either PHP or Perl. In this article, we start with Perl implementation. This is much simpler because of the availability of many quality packages from CPAN, the Comprehensive Perl Archive Network.

Using Class::DBI

The Perl library that interfaces with database is the Perl DBI. It is already quite easy to use. However, we can go a step further by using the Class::DBI package from CPAN which provides higher level of abstraction.

With Class::DBI, each table maps to a different class that derives from a common class. In the common class which derives from Class::DBI, you can define the database, user name and password that is shared by all the derivative class. The code looks like this:

package Voting::DBI;
use base 'Class::DBI';
Voting::DBI->connection("dbi:mysql:voting", "username", "password");

Here we assume you use MySQL and you need to change the database name, user name and password to match your local environment.

For individual tables, you define a class that links with the table's name and its columns. For example, for the candidates table, you can define the class as follows:

package Voting::Candidates;
use base 'Voting::DBI';
Voting::Candidates->table('candidates');
Voting::Candidates->columns(All => qw(id name email bio));

By default, the first column in the All list is the primary key. If the primary key contains multiple columns, then you can use Primary and Others to specify the key and non-key columns. You can download the sample file that defines all the classes that are associated with the tables for the voting page and rename it to DBI.pm.

Once you define classes like the above, you get some handy functions such as retrieve_all, search and etc so you don't need to write any SQL statement for simple operations. For example, you can define a Perl function that retrieves all the candidates' information as follows.

sub retrieveCandidates
{
  my @candidates;
  my @results;
  @candidates = Voting::Candidates->retrieve_all;
  foreach my $c (@candidates) {
    my $rec;
    $rec->{id} = $c->id;
    $rec->{name} = $c->name;
    $rec->{email} = $c->email;
    push @results, $rec;
  }
  return \@results;
}

This function returns the reference to a list of references that store individual candidates.

Similarly, you can define the following supporting functions:

Please note that all these functions should be implemented such that it doesn't need to be called and tested from Web/Mason context so you can test them easily. This implementation strategy also makes them reusable in other contexts.

You can download the sample file that contains all the utility functions to support the online voting pages and rename it to Utils.pm.

Implement the voting pages

From the top level, we can have two Web pages: One handles the voting, the other handles the request for sending voting code. We name the first one vote.html and the second one vote_request.html. Because the request page is much simpler, we discuss it first.

The vote_request.html page actually needs to handle three states: Initial page load, error and success. Initial page load is the one shown to the user who visits it for the first time. Error is the one shown to the user when he/she provides a non-existent email or an email has been sent recently. Success is the one shown to the user when the voting code has been sent successfully.

We organize the vote_request.html page as three parts. The first part is the Mason <%init> block that is called first but it is put to the end of the file. This one first checks whether a user has provided a valid email address through the helper function sanitize_email_input. In case the user just loads the page, there is no form submission and it just returns the results in two variables. The variable $status is 1 if it is valid, otherwise it is 0 and error message if any is stored in the $msg variable. The special hash variable %ARGS stores all the user's input. If the user provides a valid email address, then the code will try to send an email. If it succeeds, then the variable $confirm is set to 1, otherwise the error message is recorded in the $msg variable.

So the first part in essence implements our business logic and fill out the values in variables that can be used by the presentation page. The second part is very simple: If the variable $confirm is 1, then it means the voting code has been sent to the user's provided email address and we just need to load the confirmation page which has little logic in it.

The second part is actually our presentation page. It is shown to the user if she just visits the page or provides an invalid email address. It uses the $msg to decide whether an error should be shown or not.

So far we have just described one possible way of organizing our page and Mason code. An improvement is that you can actually move the sanitize_email_input function to the Voting/Utils.pm file so the vote_request.html can be much simpler. You can also merge the first part and the third part together.

If you like to label patterns, you can actually regard the combination of the first and the third parts as the controller and the second part as the view and the functions implemented in the Voting/DBI.pm and Voting/Utils.pm as the data model. We just expect the current organization to be very simple and straightforward after all the explanations. :)

You can download the source file for the page that a user can request the voting code for a given email address and the source file for the page that confirms with the user that the voting code has been sent for the details.

In addition to this, you will also need to configure apache to use Mason to handle requests to these pages.

First you need to load the Mason module for mod_perl environment. You can create a separate file such as mason.conf and put it in your apache's configuration directory. The file should contain the following:

PerlModule HTML::Mason::ApacheHandler

Then you need to tell where Mason component code resides.

PerlAddVar MasonCompRoot "main => /var/tmp/local/apache/htdocs/vote"

Most of the time, this should be the place where your HTML files that contain Mason blocks reside.

Next you specify the URI where Mason will be used to handle the requests.

<LocationMatch "/vote/.*(\.html|\.txt|\.pl)$">
  SetHandler perl-script
  PerlHandler HTML::Mason::ApacheHandler
</LocationMatch>

You don't want Mason to handle requests for all the files, e.g., image files in that directory otherwise it will slow down the process of downloading these files.

In your Mason configuration file, you can also preload all the dependent libraries and functions that your code use so performance can be improved. Please note that you need to put them all under the namespace of the package HTML::Mason::Commands. The configuration block looks like the following:

<Perl>
{
  use lib qw(/directory/that/contains/Utils.pm/and/other/files);
  package HTML::Mason::Commands;
  use URI::Escape;
  use Voting::Utils qw(
    check_id_in_list
    check_email_in_list
    ...
}
</Perl>

Please note that if you don't install your Perl module files in the standard Perl lookup directories, you need to add the use lib qw(...) to include that directory. You can find out Perl's default include directories by running the following command:

  perl -e '$,="\n"; print @INC;'

You can download the sample Mason configuration file for the voting pages for details.

We can implement the actual voting page similarly. The voting page needs to handle three states too: Initial page load, error and success. The first one is the initial page shown to a user who visits it for the first time. The second one is the error page a user gets when the user tries to vote but something goes wrong. For example, the user may fail to provide an email address, or the email address is not in the member list, or the voting code is incorrect and etc. The third one is the confirmation page shown when the user has successfully voted. It can include names of all the candidates that the user has just voted.

The file needs to be divided into three parts too. The first part is put into the Mason's <%init> section. It basically validates user's submission. If there is no user input or user's input is valid, it simply flows through to the second part to show the page with optional error messages. As part of the validation process, it also needs to retrieve the list of candidates and check user's selections against it.

All the information a user submits through either HTTP GET or POST request is available to Mason in the hash variable %ARGS. The function sanitize_user_input does all the input filtering and validation. If it succeeds, then the insert_voter_result function is called which inserts the results into the table and also populates the $ARGS{'candidates'} variable. This is later picked up by the confirmation page to show the candidates that the user has just voted.

You can see the source file for the voting page and the voting confirmation page for details.

You may also notice that in the voting page we have one line that looks interesting:

  <& /CandidateHelper.mas, %ARGS, candidates => $allcandidates &>

This actually calls a Mason component named CandidateHelper.mas which accepts the %ARGS and @candidates variables as input. As we have said earlier, the %ARGS hash contains user's input. In it the $ARGS{'candidate'} variable contains either a candidate ID (a Perl scalar variable) or the reference to a list of candidate IDs (a Perl array variable). The @candidates variable comes from the dereferenced value (array) that comes from the $allcandidates variable which is a reference to all the candidates. The Mason component basically displays a list of checkboxes with candidates' names next to them and remembers the user's choices if there is anything wrong with the user's other input such as unregistered email address and etc. You can download the Mason component that shows a list of candidates, rename it to CandidateHelper.mas and copy it to the same directory as your other Mason enabled HTML files.

Because of limitation of our shared Web hosting environment, we cannot demo how the voting pages looks like. We will probably show how to implement the voting pages in PHP later.

Back to articles