Such a short title, but a major section. Buckle your seat belts, the changes I am going to make here are going to determine the structure of the application for the foreseeable future. That is not necessarily to say that there will not be any more changes in how the menus are generated, but that any such changes will represent enhancements to the essential structure presented here. The notion of structure itself is going to be something much more flexible than it has been previously, and from that perspective this is just one more step along the road.
Regardless, this section is going to involve a lot of material. Since, however, most of it involves evolutionary use of components that already exist, I do not want to break the discussion up into sections. Rather, this discussion will parallel the process I undertook as I restructured the application. Despite all the fancy catch phrases that are used to describe any given step in a development process, it has to be "lived forward". (That comes from the saying that life is best understood in retrospect, but it has to be lived forward.) That means that in actual concrete circumstances, one starts with a more or less detailed specification of what is required, and then figures out a way to get there. In truth, the more detailed any initial specification documents are the more likely they are to be filled with misconceptions. As I have mentioned elsewhere, it is almost a cliche that users at whatever level primarily define their det of desired characteristics for an application by identifying what they do not want, and that as a result that specification will become more accurate as the application structure evolves. In that sense, effective specification is living specification, subject to modification. In fact, it seems to me that it is precisely the kind of structures being built here that are refined into meta-structures as a given application domain is addressed in later generations of applications. This is, of course, only my opinion, but hey ... I am the one writing this.
The point to all of that is that one should never be afraid of jumping into an application with only a basic sense of what the end state should be, and only a basic sense of how one is going to get there. Anyone who thinks that inappropriate has never "walked the walk".
In any event, as I launched into this my first consideration was the most coherent manner in which to extend the menu. Recalling the basic scenario for application access in which the same address would be used by both the general public to access game and team information and by league personnel at various levels to perform administrative functions, it made sense to me to structure the menu accordingly. Therefore, the main menu now simply offers the choice between public or league functions. As I began to do in the previous section, the menus here are each generated from text files with the construct_menu() subroutine.
my $menu_file='/home/www/bb_lib/main_menu.txt'; my $public_menu_file='/home/www/bb_lib/pa_menu.txt'; my $league_menu_file='/home/www/bb_lib/la_menu.txt'; my $game_menu_file='/home/www/bb_lib/game_menu.txt'; my $main_menu=construct_menu($menu_file,"100%"); my $league_menu=construct_menu($league_menu_file,"97%"); my $public_menu=construct_menu($public_menu_file,"97%"); my $game_menu=construct_menu($game_menu_file,"99%");This operation is performed for each of the menus in the main portion of the BB_STACKED module, each menu being loaded into a scalar. (Currently, the only menu paths that do not run into a dead end somewhere are those that support the functionality being implemented here. I will implement something to prevent errors from the selection of options for which actions are not yet configured, an error handler, in a later section.) As is evident, I am using slightly different table width values for the menus, primarily to provide an added dimention of visual differentiation between the menus. The results of that will be apparent as I shortly begin to include graphics, at this point I am simply experimenting with different values.
Once I decided to structure the menu system in this fashion, however, it became necessary to develop a URI naming scheme that would support such a system. Recall that the option selection sched relies on the strings returned by the path_info method of the request object. It therefore makes sense to have that string embed all of the information required to fully identify the operation associated with an option, but also to lead to the execution of attendant operations that may apply to a set of options to which the pertinent option belongs. This sounds more complicated than it really is. Conceptually, it is only slightly different than calling league functions from a URI that required authentication. Suppose, for example, that I were to have a set of operations involved with finalizing the results of a game ... maybe certifying the scoring and uploading results to a parent server. Selecting records from one or more database tables could be a common operation for both of those operations, and the application would be cleaner if that condition were to be placed in the code so it clearly applied to both options. Doing things this way makes it very much easier to trace problems. While the application as it currently exists does not encompass such contexts, it would not be at all surprising if that capability were useful at some point. Quite simply, it just makes sense.
Currently, the best example of this structuring is represented by the menu items included in the league menu.
Schedule MaintenanceZzZ/bb_app/league/la-sched Team MaintenanceZzZ/bb_app/league/la-team Game MaintenanceZzZ/bb_app/league/la-game User Settings<br> and PreferencesZzZ/bb_app/league/la-prefAs can readily be seen, for each of these options the path information returned after the URI is parsed will consist of a string prefaced with "la-", representing "league area". (Pretty clever, huh?) The public area options included in the file pa_menu.txt include path elements that begin with an analogous "pa-" string. Selection of one or the other drops the processing of any given request through a hole in the main() subroutine in the bb_stacked module, triggering execution of either the league or the public subroutine. The pattern matches that make this determination,
if ($path=~/^\/pa/) {
&public($path,\$r,$public_menu);
}
if ($path=~/^\/la/) {
&league($path,\$r,$league_menu,\%params,$game_id,$num_forms,$games);
}
are anchored to the beginning of the string, and include the slash that begins the $path string, which is the reason for the backslash between the anchoring caret and the second slash. That escaping backslash tells perl not to take the slash following as a slash, which would be interpreted as representing the closing of the pattern match, but to interpret it as a text string. Within the league and public subroutines execution is determined by the portion of the string after the dash. The main() subroutine is thus much smaller now, and the subroutines that process the requests in more detail have a more coherent and hierarchical structure. I also have a great deal more freedom in how I name URIs. I no longer have to be concerned with making a string in the path information globally unique, it only has to be unique in the last subroutine in which the request is submitted. Thus I could use the string "doo-wah-diddy-diddy-dum-diddy-doo" in two seperate URIs, as long as I prefaced one with "pa-" and the other with "la-". Note, however, that a /diddy/ pattern match in the same subroutine would be tripped along with the /doo-wah-diddy-diddy-dum-diddy-doo/ match, unless nested in a structure that prevented it.Now that I have created a new structure to access the entry process (through the trail /league area/game maintenance/entry) but I have not yet addressed the issue of setting the game_id. (As I have set a default value for $num_forms, it is not necessary to establish that value befor entering the entry process.) Under this structure, I have stuck setting such values under the settings/preferences option in the league area.
The page generated by that option looks like the page at right. Obviously, the displayed number of forms can be selected on the left, and the game being scored on the right. I ultimately intend the give users the ability to select from a set of color schemes under which pages will be generated, and that selection will occupy the space in the middle.
Just as in the previous version, the path information in the action parameter of the form generated by the preferences() subroutine contains the string "logged",
$$forms->print("<p><form action='http://ralphzilla/bb_app/league/la-logged'>");but it is also prefaced by "la-" and references the /bb_app/league directory, which will require that the session has been authenticated once I make the requisite changes to the httpd.conf file. While submission of this form does not represent a significant security concern beyond the potential for a very knowledgeable saboteur to change the game id being recorded, my thought is that it is a good idea to get into the habit of keeping the exchanges of information associated with the actions for which authentication is required within the authenticated area. I will therefore be much less likely to create a security hole by allowing the submission of key data to a URI that does not require authentication.As in the previous version, a pattern match on "logged" triggers the execution of the save_state() subroutine. In this version, however, I am no longer using cookies to maintain state. The reason for this change is, essentially, two-fold. First, as I worked though the various things that bear on problems associated with cookies on my initial implementation of this I found myself think both about the support involved with making sure that browsers are appropriately configured to handle cookies. Given the direction in which certain elements of the application are likely to move, the second reason behind moving away from cookies became clear ... I was going to do it anyway. As an example of that direction, it is likely that at some point there will be system administration functions developed that involve listing logged-in users and the games that they are entering. Given that this will required some level of centralized state maintenance, I decided that I might just as well move toward that. (This excepts, of course, those elements that are associated with a given data entry session and that are passed around as hidden fields.)
There are, of course, a number of approaches that can be taken to server-side state maintenance. I was giving some consideration to the lightweight options available when I realized that I already had a database created, and therefore all I really had to do was create a table to hold the user state information. As the number of system users will not be all that large, and only one row (record) in that table will be associated with any given user, retrieving pertinent state values will be fast. (Note that inherent in this model is the notion that a user should only be logged into one session at a time. If I wanted to allow multiple concurrent sessions for individual users I would have to track the state information by an identifier for the individual session, probably the unique session id maintained by apache. That, however, is not a good security policy. It implies that multiple individuals would be performing system functions as the same user, and that is something that should be done only when there is no other choice.)
As "user" is not allowed as a postgresql table name, being reserved for the system table used to track database users, I named my table user_char. The initial structure of that table is as follows:
user_id char(5) unique, user_full_name char(30), password char(20), game_id char(5), num_forms char(1), c_scheme char(100))Obviously, given the password column, I also plan to use this table in the authentication handler. The database also incorporates a unique index on the user_id column in this table, which means that only one row will exist for any given user_id. The script to create this table is included in the samples file as user_table.scr, and may be piped into the psql client utility (e.g., "psql -h ralphzilla-raider -U baseball baseball < user_table.scr") to initialize the table.
The save_state() subroutine now becomes a matter of updating the user's row in the table. When I first coded this section I updated the record with a $dbh->do() statement, subsequently refined to the statement handle version used here. My primary reason for mentioning that is that the $dbh->do() statement requires quoting the values used in the statement, while the $dbh->prepare() method takes care of that for you. As a result, however, values prepared with a $dbh->quote() statement will not work appropriately with a statement handle prepared with $dbh->prepare(). If you have not yet realized this, some may have gotten a version of this page and sample files before I realized the inconsistency. Now you know why that version would not have worked appropriately<grin>. The database handle is created in the main portion of the module, a subject I will discuss shortly. As I have not yet dropped the authentication handler into place there is not yet a way for the user id to be made available within the script, so for testing putposes I have been hard-coding "llane", the user id for Lois Lane, into those places where that value is required. Once the elements are in the appropriate format I update the user's row in the table with the selected game_id and num_forms values, assigning the result of that operation to the $_store scalar. (Variable names beginning with underscores like this are generally used to connote a temporary working variable local to a subroutine.) The value in $_store then determines whether a confirmation or a warning is returned to the browser.
As I wrote the last paragraph it occurred to me that one advantage the application has had has been the ability to function in an environment decoupled from the database server, and by storing state in the fashion I have here that is no longer possible. On on hand this is not all that significant. The presence of an apache server is implied in the environment, and virtually any environment that can run a web server can also run a database server without problem. On the other hand, I can also see the appeal of a version that could run in a de-coupled fashion, and I am intrigued by the challenge of creating a version that determines the environment in which it is running and adjusts in response. While I have added that to my enhancements list, I think it is quite possible to overestimate the size of the niche for this kind of thing. I might use the example of a hypothetical league that wants to record games in a wide number of locations. While it will be possible to create a version that would run on a mod-perl enabled server on the same machine used for entry, it could be just as inexpensive and far more effective to set up a portable server with a wireless network connection, thus freeing the local client to handle the interface and creating a more functional context that would more readily allow use by other users at the site.
Returning to the application, as I said earlier the database connection is set up in the main part of the module.
use DBI;
use DBD::Pg;
my $dbh=DBI->connect("DBI:Pg:dbname=baseball;host=ralphzilla-raider",$db_user,$db_pass) or die "cannot connect to database";
my $get_user=$dbh->prepare("select * from user_char where user_id=?");
my $update_user=$dbh->prepare("update user_char set num_forms=?, game_id=? where user_id=?");
In the example above I am using the standard DBI module to set up the connection to the database. This is not desireable as a final configuration, if only because this requires that a connection to the database be established as each client connects, but I want to start from this point to better illustrate how easy the transition is to a more appropriate configuration. I will get to that in just a bit. After creating the connection to the database, I prepare statement handles that are used to retrieve and update the state information in the database. As I have before, the name I have given to the handles here violates informal conventions for naming statement handles, but especially in this context I found the descriptive statement handle a compelling choice.
If pertinent, state values are returned early in the body() subroutine.
$user_name=$r->connection->user;
$user_name='llane';
if ($user_name) {
$get_user->execute($user_name);
my @user_settings=$get_user->fetchrow_array;
$game_id=$user_settings[3];
$num_forms=$user_settings[4];
}
When the authentication handler has been incorporated, the presence of a userid in the connecion object will signal that this is an authenticated session, triggering the retrieval of state information. In this version, as the authentication handler has not yet been activated that command will always return undef, so I set a $user_name value statically for testing purposes. The presence of that value will therefore trip execution into the same state it would occupy in an authenticated session, with the $get_user statement handle executed with the $user_name scalar as the placeholder value. The returned result set is read into the array @user_settings and the fourth and fifth elements of that array assigned to the scalars $num_forms and $game_id. There are two aspects of the wider system that allow me to execute that statement without an error condition, and to freely assume that only one row will pertain to any given individual user_id. First, remember that this table will be used in the authentication handler. Therefore, if an invalid user_id is entered, the session will not be executing this bit of code ... that row will either exist in the table or this will not be executed. Second, the unique index on user_id assures that only one row for the user_id will exist, and that therefore the result set returned from the executed statement cannot have more than one row.Once all of the elements have been put in place, navigating to and starting an entry process will look like the sequence in the following screens.



To return to the issue I raised just a bit ago, making database connections, when the DBI module is used to connect to the database, the connection must be initiated on each pass through the handler. Not only can this represent a bit of a performance drag, but it can eat up resources on the server host, especially if occassional sessions are not closed appropriately. The Apache::DBI module was developed to address this. Apache::DBI will set up a cache of persistent connections that are returned to DBI->connecty statements, thus removing the background overhead associated with making the connection each time a request is processed.
This module is generally set up in the startup.pl script, as follows:
use Apache::DBI;
use DBD::Pg;
my $db_user="baseball";
my $db_pass="baseball";
Apache::DBI->connect_on_init("DBI:Pg:dbname=baseball;host=ralphzilla-raider",$db_user,$db_pass) or die "cannot connect to database";
Given that setup, each time the DBI->connect statement is issued the request will be passed to Apache::DBI, which will return a connection. While it is also possible to load Apache::DBI with a PerlModule statement in the httpd.conf file, in earlier versions of mod_perl there were problems associated with that in certain contexts. Since using startup.pl allows the creation of a connection on server startup, it is the better choice regardless. The limitation to this is that all users must be connecting to the database with the same username and password. This is not a problem with this application as it currently envisioned, but could be in other circumstances. In those cases a seperate mod_perl-enabled server would have to be run without Apache::DBI loaded listening on a different port, and the URIs that control application execution would have to switch back and forth between the servers.Now I have a worksable and working interface with a great deal of flexibility, and the entry process working within it. In the next section I will drop a real authentication handler onto the structure, and maybe set up some sort of color selection scheme. I personally think that displaying the multiple menu levels at the top of the page is beneficial to site navigation, but I do think that it might be nice to have all of the menus under the public area display under one color scheme, while the menus under the league area display under another. That is the kind of visual clue that can really help a user to navigate the site. Given that, I will probably want to provide users with a limited set of options that do not conflict with the colors I set for the menus ... guess you'll have to read on for that, huh? <grin>