Sounds impressive, doesn't it? Actually, this chapter is going to be primarily involved with housekeeping in that the main focus of the chapter will be on creating hashes and other structures to house related elements that get passed around within the application. (People tell me that actual physical housekeeping is a lot like that <rueful grin>). I recognized a few chapters ago that the manner in which I was passing arguments within and between modules was becoming unwieldy, and sometimes confusing. As an example, parameters are currently carried in two seperate structures, the %params hash populated simply by reading in the output from the request object's args() method in the body of BB_STACKED and the hash of arrays of the same name local to page_proc() in BB_APP_INTERFACE and the subroutines it calls. This is not a desireable state of affairs, but it is the type of thing that is common as applications are developed. While appropriate to address, it should also be recognized that doing so too quickly will require that it be done again, and sometimes again. In some circumstances, that might be perfectly appropriate. Imagine, for example, that I am pressed for time and want to distribute an initial version of this application that would allow data entry, and perhaps modification of entered records, but that time does not exist to implement the insert and delete capabilities. In other words, imagine that I am pressed for time, but that I do want to make the overall structure of the application as legible as possible should some degree of local maintenance be required. Even under some time pressure, it would be appropriate to spend a few hours consolidating elements in the fashion I am going to employ here. As an illustration, however, a comparison of the application as it existed a few chapters ago with the application state at the end of the last chapter would reveal a number of changes in the way subroutines pass arguments around. That means that if I had conducted some element consolidation at the point described in that earlier chapter I would soon have to be ripping it apart and re-doing it. While that would not have been an overwhelming task, by waiting until now I have gained the advantage of having worked through a major portion of the application's functionality. Therefore, the chances that some degree of significant modification will be required at some point is substantially reduced.
While this chapter and the activity it discusses are not as wildly exciting as previous chapters <grin>, this is the stuff that separates the amateur from the professional. While it is always possible for untoward circumstances to intervene, an application intentionally left in a state that is difficult to understand for any reason other than the unavoidable has not been professionally handled. The kind of thing that I will be doing here is a necessary but not sufficient condition to the development of an application that is internally coherent.
Rather than making wholesale changes in one fell swoop, I am going to trace the changes occassioned by the construction of individual structures as they are developed. In some instances, this will simply be a matter of extending a structure developed in one context into wider use, as will be apparent shortly. The initial step I am going to make involves dropping references to the database statement handles defined in the main part of BB_STACKED into a hash that will be used to access them as appropriate. I am not, however, going to worry about making those changes wherever one of the handles is accessed in BB_STACKED, because they should be visible in that same scope in which they are declared.
The hash holding the references is created immediately after they are initialized
my %db_handles=(user_info=>\$get_user,
user_update=>\$update_user,
edit_game_records=>\$ed_recs,
update_game_records=>\$update_record,
insert_blank_game_record=>\$ins_blank,
delete_record=>\$del_recs);
my $db=\%db_handles;
I use descriptive text as the key in the hash, which should result in code that
is inherently legible. After creating the hash i store a reference to it in
the scalar $db, which is what I will pass as an argument to the subroutines, as
in
&page_proc($r,$num_forms,$user_name,$colors,$db);, where the hash reference is the final argument. Within the subroutines, I access the hash to retrieve the reference associated with that database handle, store that reference to a generic scalar named $dbh, and access the statement handle through that reference as in the following example:
my $dbh=$$db{'delete_record'};
$k=$$dbh->execute(${$$params{'key'}}[$i]);
Note that the scalar holding the reference to the statement handle must still
be de-referenced; hence the second dollar sign preceding the scalar in this
example. I gave brief thought to accessing the reference from its location
within the hash rather than pulling that reference into a hash as I do here.
However, after a few minutes of trying to assemble the appropriate
dereferencing statement I decided that the two statement version as employed
above is far more legible than anything I might string together in a single
statement. A review of the current version of BB_APP_INTERFACE will reveal the
other locations in which corresponding changes have been made, and should make
it clear that, as with most of what is discussed in this chapter, this is just
a matter of packaging.
The second set of elements that I grouped together was the set of hash references that I use to construct the select boxes in sel_box(), which is called by base_form() during the entry process and by rec_line() during the edit process. As these references are used together, rather than being accessed individually as are the database handles discussed above, I simply roll these four references into an array
my @select=($events,$role,$participants,$result);and pass the array as an argument, as in the new version of the call to edit_page().
edit_page($r,$colors,$params,$db,@select);
The impact that the two changes I have made is having can be readily appreciated if the statement above is contrasted with the version used in the last chapter
edit_page($r,$colors,$params,$ed_recs,$role,$result,$events,$participants);That difference is representative of the changes that will cascade throughout the application as these container structures are introduced. In some circumstances, the impact could be much more dramatic.
The benefits of building container structures are also very evident as execution is passed into edit_page() or get_form(), the subroutines in BB_APP_INTERFACE that generate edit and entry pages, respectively. In each of these cases, the references in the array are not needed in the subroutine itself, but are passed through into sel_box(). In the entry process this is a straightforward process; the @select array is passed into base_form() as the second argument to the subroutine, and base_form() simply reads the array as as individual elements from the argument list. In the edit process, however, the context under which a page is drawn is slightly more complex. As this page is being generated based on values that have already been entered, I must either pass rec_line() a long line of arguments as I did previously or I must somehow get around the limitations imposed by the default manner in which perl reads arguments into a subroutine. The solution that I adopted was to simply pass references to the @select and @rec (the array holding the values from a given table row) arrays.
rec_line($r,$check,\@select,\@rec);Within each execution of rec_line(), I must now populate the elements printed to a line with values from the arrays to which those references print. That process is generally accomplished in the same manner as that used as when accessing the values in an array directly, but in this case the array must be dereferenced. As illustrated in these lines
my ($key,$participant_id,$team_id,$game_id,$role_code,$inning,$half_inning,$inning_order,$event_code,$event_result_code,$event_text)=@$rec; my ($events,$role,$participants,$result)=@$select;arrays can be dereferenced simply by sticking an "@" in front of the scalar holding the reference.
The next modification I made was to generate a common hash of parameters to be used throughout the application. The two or three different parameter accessing strategies employed at one point or another within the application were, of course, developed in response to requirements for greater flexibility in accessing those values. To a certain extent, I could leave the application in its current state and it would continue to work well, and as there is only a modest amount of duplicated code associated with the various strategies the amount of additional overhead is small. The real cost associated with incorporating multiple methods of doing the same thing has to do with application maintenance, especially as that responsibility is assumed by individuals external to the original development effort. As I have developed the functionality described in subsequent sections I have on occassion found myself looking at a statement utilizing one or another of the formulations, briefly thinking "how the < expletive deleted > can that work?". And I wrote the thing <grin>. While this step is not going to do much to reduce the number of arguments that I am passing around, it will contribute significantly to the application's clarity.
This implementation is generally simple, but something of a pain as all parameter accesses are changed to adopt the common format. Since the entry and edit processes require that the parameter structure allow the use of multi-valued parameters, this requirement must determine the structure employed. As I have already written the code required to implement such a structure, all I have to do is move it from the beginning of page_proc() in BB_APP_INTERFACE to the beginning of the body() subroutine in BB_STACKED.
my $r=shift;
my @args=$r->args;
my %params;
while (my($name,$value) = splice @args,0,2) {
push @{$params{$name}}, $value;
}
my $params=\%params;
Note that I no longer initialize $r as an Apache::Request object. As I am
creating a structure for multi-valued parameters, I no longer require the
capabilities provided by that module. Once the hash is created, I store a
reference to it in the scalar $params, and as in previous versions it is that
scalar that is passed around.
The tedious aspect of this modification manifests itself when all parameter access statements are changed to correspond to this structure. While the use of multi-valued parameters is key to the operation of the application as it is, the vast majority of parameter accesses are for parameters that have but a single value, and that value is being accessed to control the flow of the application. In such situations, the statements must be changed to access the first element of the array to which the hash key points. In some ways, this change is subtle enough to be easy to miss, involving the modification of something like
elsif ($$params{'action'}=~/Re-Disp/)
to
elsif ($$params{'action'}[0]=~/Re-Disp/)
As virtually all text editors have a search function, it is a simple matter to
search on "$params" and find lines containing that string quickly. The form I
had previously used less frequently, accessing through the Apache::Request
object, must also be changed, as illustrated by the modification of
my $session_id=$$forms->param('session_id');
to
my $session_id=$$params{'session_id'}[0];
In some cases, I use a form slightly more complicated than that above, left
from the earlier part of the previous chapter.
$s_half=${$$params{'s_half_inning'}}[0];
These forms are functionally equivalent. I will leave determining the basis
for that equivalence to the reader. Any readers in need of a hint might want
to review the lines of code in BB_APP_INTERFACE starting with line 95 and
extending through line 103.
Next, I am going to wrap up the information about the logged-in user. In the current structure of the application, user information is retrieved if the user has been successfully authenticated. In previous versions, I would then populkate a set of scalars with that information and pass those scalars around as appropriate. In some cases, this contributed substantially to the length of the argument lists that I was passing into subroutines. At some point I realized that I could simply leave those arguments in the array populated by the fetchrow_array() method and access them from the array.
The current implementation of the associated database access snippet is as follows:
if ($user_name) {
my $dbh=$$db{'user_info'};
$$dbh->execute($user_name);
@user_settings=$$dbh->fetchrow_array;
$num_forms=$user_settings[4] if $user_settings[4];
}
In this version I am using the hash of statement handles to create the
appropriate database handle, and as before storing the returned row into the
@user_settings hash. The only scalar that I continue to manipulate here is
$num_forms. As before, I do not overwrite the default $num_forms value unless
the user has specified a value.
While this sounds like a very logical context in which to use a container, it also illustrates the limitations of the technique and why, at least at this point, I feel that I have reached the point of diminishing returns in their deployment. As these settings are only pertinent within the league area, in this version I pass a reference to the scalar as an argument when I call the league() subroutine. (Remember, it is not merely true that nothing in the public area makes any use of any of these user-defined settings; under the current implementation nothing under that path can make use of any of these settings. As the existing authentication handler is operative on anything under the league path, the connection information used to access that information is not available unless the page is being generated under that URI. This is a trade-off associated with the manner in which I have implemented the public/league dichotomy within the application. If at some point I deem it appropriate to have some settings remembered under the public area I will have to implement another strategy that will be operative under that URI.)
Within the league sburoutine I read the reference to the array into the scalar $user_settings, and from that point either pass that scalar along into other subroutines
&game($path,$r,$game_menu,$user_settings,$num_forms,$params,$colors,$all_games,@select);or dereference a specific element of the array and pass that along
&page_proc($r,$params,$num_forms,@$user_settings[1],$colors,$db);The game() subroutine is currently the lowest level at which the reference is treated as a single item, with dereferenced elements being passed onward. A handy structure, but its impact is largely limited to the subroutines within BB_STACKED.pm.
At this point, I think that the only thing that would make a significant improvement in the number of arguments being passed around would be to wrap up everything other than purely local variables and pass that big structure around. That would be kind of cool, in a way, but I think that the applicability os such a large structure would be limited because I have generally reached the point at which relatively little additional functionality will be added to existing functions. Further development of the application will generally be horizontal, as it were, extending into new territory. I may, however, change my mind about that at some point.
It is time to discuss the bug I unintentionally left behind in the last chapter. In my obsession with appropriately handling the addition and deletion of records in the version of BB_STACKED.pm that is included with the previous chapter's sample files I did not appropriately address the actions required for storage of records entered as part of the entry process. I spotted the problem several days later as I was testing the functionality of these references, but at that point decided to simply mention it as a challenge for those who might decide to take it.
To quickly review, any page that modifies or enters a set of records does so within an html form for which the specified action is
action='/bb_app/league/la-store'Once the URI is parsed, of course, the string "la-store" finds its way into the $path scalar, which in this circumstance led to the execution of this specific bit of code in the league() subroutine (remember, this pre-dated the changes I made above)
if ($path=~/store/) {
&page_proc($r,$num_forms,$user_name,$update_record,$del_recs,$colors,$ins_blank);
}
Now this is all well and good, but a review of the version of page_proc()
incorporated in that version of BB_APP_INTERFACE.pm reveals that absolutely
nothing within that subroutine traps a data entry context.
DUH!
As a result, submitting a page of entered records would simply result in the
entered information being lost, followed by the generation of the intermediate
page that follows the submission of an edited page.
There are, of course, any number of ways to go about dealing with this, but having just had to figure out what was going on here I decided to adopt a straightforward approach by simply directing execution appropriately from within the league() subroutine in BB_STACKED.pm.
if ($path=~/store/) {
if ($$params{'task'}[0] =~ /enter/) {
&store($r,$params,$num_forms,@$user_settings[1],$colors,$db);
}
elsif ($$params{'task'}[0] !~ /enter/) {
&page_proc($r,$params,$num_forms,@$user_settings[1],$colors,$db);
}
}
With this change, everything works as desired. As I suggested, there are a number of ways to do this, including putting the appropriate conditions into page_proc() to handle the entry process. Such an approach would probably also involve placing the code that generates the intermediate page associated with the entry process into page_proc(). In the long run, that is a better design, as it involves keeping the store() subroutine associated with directing record storage and the page_proc() subroutine involved with determining the relevant actions to be performed with the submitted object. One might take that as a hint about relatively near furture developments in this section of the application.
So that's it for this chapter. At this point, I am not sure what direction I am going to take with the next chapter; whether to add functionality under the currently unpopulated options, start adding some basic validation capabilities to the client, or something else altogether. I'm going to take a few days to stew over that one. As the reader, however, you have a real advantage. All you have to do is click on the next menu option. (If it is there, of course.) <grin>