Document Home

Previous Managing Teams and Players
Previous Developing a Generic Editing Table Subroutine
Samples for this section

Managing Team Records



Now that I have created edit_table(), I can use it to create an interface for managing the team records. In all honesty, I knew that the availability of the subroutine was going to simplify this process, but I was more than a little startled by just how much easier was the process of constructing the basic interface. In fact, it was so much easier that I am going to use the bulk of this chapter to work through the creation of a generalized subroutine to process the submissions from pages generated by edit_table().


When I initially sketched out my plan of attack for managing teams and players, I had planned to generate a page that would revolve around a two-cell table, with editing tables for teams and players in the cells on either side of the page. I had forgotten, however, just how much data is involved with the contact information for a team, and came to realize that it is not going to be possible to do both that and use edit_table(), which presumes a one-line per row output structure. If the nature of this application were such that a page generated in such a fashion were key to its functioning, I would likely either create a version of edit_table() that was configured to output tables holding multiple lines, or do it with a custom subroutine somewhat like get_form(). In this context, however, I do not consider that necessary. As teams, and to a lesser extent players, are only likely to be added to the system once a season, it makes far more sense to me to create a clean interface that is also relatively easily maintained. It is therefore likely that I will ultimately layer another menu under the teams option on the league menu, but for the purposes of this chapter I will just call the subroutine from that point.

	if ($path=~/team/)	{
		
		teams_players($params,$r,$colors,$db);
		
	}


Within the subroutine itself,the scalar $table is initialized to 0 immediately after reading in the arguments passed to the subroutine. Here, as in the next chapter in which I develop a page on which player data can be managed, differentiation between sets of widgets for successive tables is not required, because I am only going to be printing one table. (At one time, back in the dim past <grin>, I mentioned the possibility of configuring the system to support multiple leagues, which one might envision as occassioning the generation of multiple tables here. If I decide to go further with that, however, I would probably implement a selection mechanism to allow the choice of the league before generating the page, and thus still only generate one table.) Strictly speaking, the inclusion of $table here is not required to maintain compatibility with edit_table(), because I could simply pass a pertinent number in the position occupied by $table in the argument list passed to edit_table(). (I could not, however, pass an empty argument in that place. I will leave the determination of just why that is the case to the reader as an exercise.) In the final analysis, I am including the scalar here at this point simply because it is easier and cleaner to do it that way.


My next step is to initialize the scalars that will hold the widget names.

	##initialize scalars holding widget names for teams table
	my $name='name_'.$table;
	my $fax='fax_'.$table; 
	my $c_fname='c_fname_'.$table;
	my $c_lname='c_lname_'.$table;
	my $address='address_'.$table;
	my $city='city_'.$table;
	my $state='state_'.$table;
	my $zip='zip_'.$table;
This is nothing new, a similar step was used in the previous chapter. It is worth noting, however, that if this subroutine were printing multiple tables this chunk would have to be within the pertinent loop to allow the name strings to be constructed with the successive values of $table.


The next step was to populate the descriptive arrays that will be used to control the manner in which table columns will be generated.

	##initialize descriptive arrays for teams
	my @a=('',$id,'hidden','');
	my @b=('Name',$name,'text','');
	my @c=('Fax',$fax,'text','');
	my @d=('Contact First',$c_fname,'text','');
	my @e=('Contact Last',$c_lname,'text','');
	my @f=('Address',$address,'text','');
	my @g=('City',$city,'text','');
	my @h=('State',$state,'text','');
	my @i=('Zip',$zip,'text','');
	
	my @t_names=(\@a,\@b,\@c,\@d,\@e,\@f,\@g,\@h,\@i);
All of the displayed columns here will be drawn with text widgets. These columns all describe characteristics of the team that do not associate with any other components of the system, and as a result it is not really necessary to use a scroll box to control the selection of any of these items. In some cases it might make sense to use a select box for the state field, (where the league was operating in a border city, for example) but given that it is not likely that there will be that many records in this table I personally would question the desireability of a scroll box here.


At this point I have to get the team records from the database and store them in an appropriate format to pass to edit_table(). The first step in that process, of course, is to prepare a database statement handle in BB_STACKED and store a reference to it in %db_hash (the hash referenced by $db.)

my $all_teams=$dbh->prepare("select team_id,team_name,fax,contact_fname,
						contact_lname,address,city,state_prov,zip_post from team order by team_name");

.
.
.

all_teams=>\$all_teams,
In the prepared statement I order the results by team_name, simply because displaying the records in alphabetic order will make locating any record to be edited much more straightforward. When the statement handle is accessed in the subroutine
	my $dbh=$$db{'all_teams'};
	my $t_res=$$dbh->execute;
	
	$$r->print("<font color='red'>Error reading database ... Contact System Administrator</font>")
					 if ! $t_res;
I use the scalar $t_res to hold the return value from the execution of the statement, print an error message if that value is 0, and if it is positive enter the block of code that structures the data and generates the page.


My first implementation was based on what most would probably consider the most basic approach, using the fetchrow_array method to return each row in the result set in an array and storing a reference to each array in turn in the @rows array.

	if ($t_res)	{
	
		while (my @temp = $$dbh->fetchrow_array)	{
					
			push @rows,\@temp;
			$k++;
			
			}		

		edit_table($params,\@rows,\@t_names,$k+5,1,$r,$colors,$table);
			
			
		}
 
This works fine, and is all that is required at the basic level to draw a page. (Hence, my earlier comment about just how easy this would be.) As an alternative I developed a more economical manner in which to go about moving the data from the result set into the edit_page() subroutine worth mentioning even though I did not use it in the final version, for reasons that I will explain shortly. That version took advantage of the DBI fetchall_arrayref method, which returns a result set in precisely the structure I use in @rows. Implemented in this fashion, the relevant bit of code is simply
		my $s=$$dbh->fetchall_arrayref;		
		$k=@$s;
Here $s is a reference to an array holding the set of references to the arrays holding individual rows, and would represent the argument to be passed to edit_table() where I have in other contexts passed \@rows. I have decided, at least for the time being, to define the maximum number of rows to be printed on any given generation of the table as five more than the number of teams returned from the database query and printed as rows. This struck me as a reasonable number of records to add to the table on a single page; further additions would require the submission of the page and the generation of another. I may make that fancier at some point, but as teams are only likely to be entered at the beginning of the season this strikes me as reasonable. In the first version I determined the number of printed rows by simply counting references I pushed onto @rows. In this version I assigned the array referenced by $s directly to a $k, which as I noted in the last chapter assigns the number of elements in that array to $k.


Each of these versions works, after a fashion, each generating a page that looks like this.



(Remember, I have intentionally left one blank record in the table so the hash derived from the table has a blank record when sorted and displayed in a select box, and that I have not yet stored information in any of the table columns other than the team name.)


Careful examination of this page, however, would lead one to wonder just what has happened to the team names; only the first word of any name is listed. Seeing that, I first made sure that the problem did not originate with the retrieval operation and storage in the @rows structure by commenting out the call to edit_table() and inserting a snippet that prints the contents of @rows.


		foreach my $t(@rows)	{
			
			foreach my $v(@$t)	{
				
				$$r->print($v);
				
			}
	}	 
This produces a page that looks like this.


Clearly, the names are in the arrays referenced by @rows appropriately. My initial thought was that this had something to do with the manner that perl parses array elements in the array of referenced arrays notation used here, which is kinda true, but as I discovered on a different subject a little later in the chapter this is only a manifestation of a wider pattern, which is that perl is seeing the words in the title as a list of words. Another DUH moment, it made perfect sense once I started thinking about how perl would look at it. I will get to the resolution I developed shortly, but first I want to point out that in the previous chapter I initially labelled the data structures I pass to edit_table() as arrays of arrays, which is not quite true. They are arrays of references that point to arrays. Obviously, the notation I used there is correct, given that the subroutine works, but the notation used in the simpler array of arrays structures is slightly different. At some point I am going to go back and edit the chapter appropriately, but I do not want anyone who read that chapter before its modification to get confused.


The workaround I adopted here was to substitute underscores ("_") in place of whitespace. I have noticed forms on web pages doing this, now I know why. I can adopt a workaround rather than some more sophisticated approach that might allow me to maintain the whitespaces because the name is just a label I am attaching to the team. The individual words that are a part of that name do not have an important role in the operation of the overall system, and could still be located by an appropriately constructed pattern match should I so desire. Indeed, were I to consider it important, I could reverse the operation and replace the underscores with whitespace. As I did not want to have underscores at the end of any given names, I decided first to strip any trailing whitespaces from the end of each element, then replace remaining whitespaces with underscores. To do this I used the original manner in which I fetched rows from the result set and performed that operation on each element in those rows.

		while (my @temp = $$dbh->fetchrow_array)	{
	
			foreach my $element (@temp)	{
				
				$element=~s/\s+$//;;
				$element=~s/\s+/_/g;
				
				}
							
				
			push @rows,\@temp;
			$k++;
			
			}
In the first line within the foreach() loop each element is replaced with the same string, but with any whitespace at the end of the line replaced with nothing. In the second line, any whitespace elsewhere in the string is replaced with an underscore. The "g" following the specified replacement signifies that the replacement should be global within the string.


After these operations, the page generated by edit_page() looks like this




All that remains at this point is to wrap the table in a form, specifying the appropriate action, include a submit button, and make sure that any required hidden fields are defined. As a result, the core of the subroutine now becomes

	if ($t_res)	{
		
		$$r->print("<form action='/bb_app/league/la-store' method='post'>");

		while (my @temp = $$dbh->fetchrow_array)	{
	
			foreach my $element (@temp)	{
				
				$element=~s/\s+$//;;
				$element=~s/\s+/_/g;
				
				}
							
				
			push @rows,\@temp;
			$k++;
			
			}

		edit_table($params,\@rows,\@t_names,$k+5,1,$r,$colors,$table);
	
		$$r->print("<input type='hidden' name='task' value='team'>
						<input type='hidden' name='tables' value=$table>");
		$$r->print("<br><br><input type='submit' name='submit'
						 value='Submit Team Modifications'></form>");			
	}
Note that the action specified is the same as that specified in other contexts in which pages of changes are submitted to the server for storage, and that the hidden field "task" contains the value "team". The pertinence of these two items will be apparent shortly.


As I indicated earlier, the remainder of this chapter will be devoted to devloping a version of the edit_schedule_proc() subroutine sufficiently general to be able to be readily adapted to any set of data submitted by a page generated through the use of edit_table(). As I have done before, I will develop the subroutine, test it by verifying that pages of schedule modifications are appropriately processed, then make sure that team modifications are appropriately stored as well.


At the outset of this endeavor I was faced with what I percieve as the lack of a clearly-justified basis on which to seperate application logic from the characteristics of the data elements being processed. In the previous chapter, for example, the nature of table construction clearly lends itself to the development of the kind of structure that I implement with @names and @rows. A moment of reflection on what an implementation of edit_page() would look like that stopped short of a fairly strong level of seperation should reveal how clunky that would be. The number of conditions embedded in the table construction statements would render the code very difficult to read and maintain. While I could create similar structures in this subroutine, their applicability would be much more limited because in this circumstance the structure of the execution of the subroutine will remain fundamentally the same, with various statements executing with values pertinent to the type of submitted page being processed. In contrast, in the subroutines that call edit_page() the information stored in @names in particular actually controls page generation at a finely-grained level of detail.


In my first working prototype of this generalized storage routine, I had nested a series of switch statements in each of the conditonal expressions that include the blocks of code that actually interact with the database table. As I was writing a paragraph that delineated my rationale for that structure, however, I increasingly felt that I could go further in generalizing the subroutine, so the version detailed here will be an extension of that gerneral theme, and will also illustrate some of the limitations to generalization in this context.


Although the overall structure of this subroutine is the same as that I used previously, to create the flexibility to make it more generalizable I must be able to specify the values of elements that are used at various points. Therefore, the number of scalars and arrays defined at the beginning of the subroutine have proliferated.

	my ($r,$params,$num_forms,$user_name,$colors,$db,$rev_team)=@_;
	my ($deletes,$updates,$inserts,$all_deletes,$all_updates,$all_inserts,$bad_id);
	my $j=0;	
	my (@update,@new,$id,$del_dbh,$update_dbh,$new_dbh,$last_dbh,$max_id,
			$next_id,@control,$team,$schedule,$id_len,$k);	
The pertinence of these items will be apparent shortly. I then implemented a switch block to set the values of scalars whose values will not change during a given invocation of the subroutine.
	SWITCH:	{
		
		if (@{$$params{'task'}}[0]	=~ /schedule/)	{
		
			$schedule=1;
			$id_len=4;
			$del_dbh=$$db{'delete_game'};
			$update_dbh=$$db{'update_game'};
			$last_dbh=$$db{'last_game'};
			$new_dbh=$$db{'new_game'};
			$max_id='9998';
			last SWITCH;	
		}
	
	    if (@{$$params{'task'}}[0]	=~ /team/)	{
		    
	  		$team=1;
			$id_len=3;
			$del_dbh=$$db{'delete_team'};
			$update_dbh=$$db{'update_team'};
			$last_dbh=$$db{'last_team'};
			$new_dbh=$$db{'new_team'};
			$max_id='998';
			last SWITCH;
		    
	 	}
		
 	}
The first scalars within each condition, $schedule and $team, will be used as flags to identify the context in which the script is being invoked. This is primarily a legibility measure. In a later version I may change that to a single scalar with multiple values. The $id_len scalar holds the length of the pertinent id for the specific context, as in team_id or game_id. This value will be used when the id generated for a new record is turned into a zero-filled string. The set of scalars holding references to the set of prepared statement handles is created to remove the requirement of incorporating the logic required to attach to the appropriate handle near the point at which it will be used. This not only improves the legibility of the code, but also reduces the amount of code executed as the subroutine loops through the submitted tables and the arrays of elements from those tables. Finally, the $max_id scalar will be used to hold the maximum value for a generated id.


The scalars described above do not require values assigned by successive iterations of the two loops in the subroutine. Near the beginning of the loop that executes while the $j counter increments over the number of submitted tables, I use an if()-elsif() construct (kinda got tired of writing switches) to assign whichever parameter array I want to use to control the execution of the loop that executes over the values submitted for a given table to the array @control.

		if ($schedule)	{
			
				@control=@{$$params{'home_'.$j}};
				
				}
		elsif ($team)	{
			
				@control=@{$$params{'team_'.$j}};

				}
This allows me to use only one foreach() statement, specifying the @control array, rather than multiple conditions within each of which the appropriate foreach() would be nested. I consider this implementation to be more legible and maintainable, at the cost of one extra statement for each submitted table. This is the only such assignment that occurs at the table level, and in one form or another must happen to establish the specific parameter arrays to be processed.


Within the loop that executes over the specific parameter array, my first step was to implement a switch within which the array @update and the scalar $id are defined with the appropriate values. (I wrote this before I got tired of writing switches.) The @update array holds the set of values pertinent to the relevant prepared update statement, while the $id scalar holds the record id.

	foreach my $t (@control)	{


		SWITCH:	{
	
		if ($schedule)	{
	
			@update=(@{$$params{'time_'.$j}}[$k],@{$$params{'home_'.$j}}[$k],@{$$params{'away_'.$j}}[$k],
				$$rev_team{@{$$params{'home_'.$j}}[$k]},$$rev_team{@{$$params{'away_'.$j}}[$k]},
				@{$$params{'game_'.$j}}[$k]);
			$id=@{$$params{'game_'.$j}}[$k];
			last SWITCH;	
			}
		
	    if ($team)	{
		    
			@update=(@{$$params{'team_'.$j}}[$k],@{$$params{'name_'.$j}}[$k],@{$$params{'fax_'.$j}}[$k],
				@{$$params{'c_fname_'.$j}}[$k],	@{$$params{'c_lname_'.$j}}[$k],@{$$params{'address_'.$j}}[$k],
				@{$$params{'city_'.$j}}[$k],@{$$params{'state_'.$j}}[$k],@{$$params{'zip_'.$j}}[$k]);
			$id=@{$$params{'team_'.$j}}[$k];
			last SWITCH;
		    
	 		}
				
 		}


The next part of the subroutine, involving deleting and updating records, looks very much like the previous implementation.

				if (@{$$params{'del_'.$j.'_'.$k}}[0] =~/yes/)	{
					
					$res=$$del_dbh->execute($id);
					$deletes=$deletes+$res;
					$all_deletes++;
					
					}

				elsif (@{$$params{'del_'.$j.'_'.$k}}[0] =~/no/)	{
					
					$res=$$update_dbh->execute(@update);
					$updates=$updates+$res;
					$all_updates++;
					
					}

This snippet provides the first indication of the manner in which the various elements defined above will be used. Under the first condition, a record flagged for deletion, the statement handle referenced by $del_dbh, holding the delete statement pertinent to the context in which the script is being executed, is executed with the value held in the $id scalar. (I have just recognized that I have been naming the scalars associated with statement handles with names including the string generally used as a convention for naming database handles, "dbh". This is, of course, purely a convention, so I am not all that concerned about that. I just want the reader to be aware that I am violating that convention, just a little. The naming convention associated with statement handles is $sth. In case it is not already apparent, I have trouble staying between the lines <grin>). Under the second condition, the statement handle referenced by $update_dbh is executed with the set of values stored in @update. This chunk is very straightforward.


The third condition, executed when new records have been entered, is slightly more complex, but not all that much if the basic structure of the subroutine is kept in mind. Remember that the first step here is to retrieve the last id used in the pertinent table, increment that value by one, and turn it back into a zero-filled string to serve as the new id. That id is then used in the set of values for the new record. In this version the switch statement at the beginning of the subroutine stores a reference to the prepared statement handle that retrieves the highest id from the table appropriate to the given context to the scalar $last_dbh. That scalar is used here to retrieve the value of that highest id.

elsif (! @{$$params{'del_'.$j.'_'.$k}}[0])	{
					
	##if the current element of the controlling array is not blank
	if ($control[$k] =~ /^\w+/)	{
					
		$id_res=$$last_dbh->execute;
		my @last_id=$$last_dbh->fetchrow_array if $id_res;
In the generation of the new id, the contexturally-defined scalars $max_id and $id_len are used to determine whether the generated new value is acceptable and to determine the length of the string holding the new id, respectively.
	##if that value is beyond the upper limit for id's
	$bad_id=1 if ($last_id[0] gt $max_id or ! $id_res);

	##if the id is ok
	if (! $bad_id)	{
							
		##increment the game id
		$last_id[0]++;
							
		##the left side of the game id is zero-padded ... get the length of the needed padding and use it to construct a string
		##holding the game id for the record to be inserted
		my $pad= $id_len - length($last_id[0]);
		my $next_id=pack("a$pad a*","00000",$last_id[0]);
Since the new id for this record has not existed until this point, I could not construct the array holding the values to be inserted until now. My next step is therefore to construct that array
		if ($schedule)	{
				
					@new=($next_id,$$params{'month'}[0].'/'.$$params{'day'}[$j].'/'.$$params{'year'}[0],
							@{$$params{'time_'.$j}}[$k],@{$$params{'home_'.$j}}[$k],@{$$params{'away_'.$j}}[$k],
							$$rev_team{@{$$params{'home_'.$j}}[$k]},$$rev_team{@{$$params{'away_'.$j}}[$k]});
								
					}

		elsif ($team)	{
						
					@new=($next_id,$$params{'name_'.$j}[$k],$$params{'fax_'.$j}[$k],$$params{'c_fname_'.$j}[$k],
							$$params{'c_lname_'.$j}[$k],$$params{'address_'.$j}[$k],$$params{'city_'.$j}[$k],
							$$params{'state_'.$j}[$k],$$params{'zip_'.$j}[$k]);
											
					}
										
which I then use to insert the record
		$res=$$new_dbh->execute(@new);
		$inserts=$inserts+$res;
		$all_inserts++;
  


At this point, I had a version that I could test with schedule modifications. This did not got quite as smoothly as my tone might indicate. On my initial tests some of the statements populating scalars and results arrays were not placed in the appropriate locations. For example, I initially had the statements creating the @new array in the same location as the statements creating the @update array. As a result, the id values stored in the table were blank, because at the time when the array was created the $new_id scalar had been defined but had not yet had the new value stored within it. As I have tried to emphasize in the last few paragraphs, in this version it is important to verify that the elements that are used to construct the scalars and arrays used here hold the appropriate values at the time those elements are constructed.


In any event, after ironing out a couple of glitches like that this version processed schedule modifications appropriately. I should then have been close to having the subroutine process team records, and in a relative sense I was. That does not, however, mean that there were not a few things that had to be worked out.<grin>


The first was relatively straightforward, but determining the best form of its resolution involved some degree of subtlety. When I first started submitting pages of team edits nothing would be processed by the server, regardless of what I had done to the records on the submitting page. I employed the standard technique of printing text strings back to the browser from various locations within the subroutine and thus discovered that the subroutine was not entering the loop that executes while the $j counter is less than the value of the tables parameter. I printed the values of those variables at a location immediately before the loop begins and discovered that both were equal to 0, the loop not executing as a result. Resolution was as simple as adding a line incrementing the $table scalar after the call to edit_page() in the subroutine that generated the submitting page, currently named teams_players(). As a result, the tables parameter will have the value 1 when the page is processed, and the submitted page will be processed.


While the resolution of that bug was relatively simple, the rationale for its adoption would have its roots elsewhere in the system. The specific manner in which submitted pages are processed was developed to conform to the requirements of submitted schedule changes, in which multiple tables should be anticipated to be routine. To recall, the $table scalar is incremented at the end of the loop that generates any given table, providing a unique basis for the names assigned to the widgets that will be associated with the next iteration of the loop. Therefore, the value written to the tables hidden field at the end of the generated page will be one more than the actual number of generated tables, with 0 of course representing the index value associated with the widget names from the first table. That is the reason why the main loop in the submission processing subroutine uses the less than comparison rather than less than or equal to.


On the other hand, it seems a little bit clunky to drop in a line incrementing the $table scalar outside of the context of the loop, so I spent a few minutes musing over the possibility of modifying things to get around it. I considered various schemes to address the issue, at one point changing the less than comparison to less than or equal to. There was more to the underlying rationale behind that than might seem to be the case, but I abandoned it because the number of exceptions it introduced required conditions within the code that I regarded as unwieldy. In the end, the solution I adopted was even more simple: simply dropping a check for that parameter value above the while() loop, setting the value of a scalar to 1 if that value is 0 and storing the parameter value to that scalar if it is greater than 0.

	my $tables;
	if (@{$$params{tables}}[0] == 0)	{

		$tables=1;
	}
	
	elsif	(@{$$params{tables}}[0] > 0) {
		
		$tables=@{$$params{tables}}[0];
	
	}	
I then use the value of $tables in the while() loop.
	while ($j <	$tables )
This may seem like a quick and dirty solution, but it is effective in its simplicity, requires no modification elsewhere in the subroutine, and removes the requirement to remember to increment $table in the subroutine that generates the submitted page. I like it. <grin>


There were two issues involved with the addition of new records, one trivial and one quite simply imponderable. After resolving the issue above I was able to edit and delete records, but any new records I had added were not inserted into the team table. The fix for the first was associated with a real "DUH!" moment. I had assigned the "team_".$j parameter array to the @control array. As the source for this parameter array is the hidden field that holds the team_id and is only printed for existing records, execution of the foreach() block would stop after the existing records were processed. As I said, very much a "DUH!" moment. I replaced that parameter array with that holding the team name and records added were inserted appropriately, or at least the new record creation process proceeded further. It did not actually complete until I resolved the next problem.


Once the insert operation actually started to fire off, the generated feedback page indicated that the inserts had not been successful. I found the following line in the /var/log/apache-perl/error.log file:

	
DBD::Pg::st execute failed: ERROR:  parser: parse error at or near "" 
	at /home/www/bb_lib/BB_APP_INTERFACE.pm line 1082, <FH> line 11.
The line in the subroutine identified here is the one that actually executes the prepared insert statement with the values in the @new array. My initial suspicion was that this problem was associated with using an array rather than a list of arguments as I had previously, but after replacing the array with the list the error persisted. I started backing the elements out of the list one by one, each time also jumping into BB_STACKED and removing one column name and value placeholder from the line that prepares the statement handle for this specific operation. The problem persisted until I had removed all of the elements but one, the newly-generated id held in the $new_id scalar. At that point I reversed that process and started putting items back in, and was pleasantly surprised when the statement executed appropriately after putting in the second element. After the statement executed with the third element in, I moved the array back into the execute statement, and once I discovered that the statement had executed resumed adding the elements to the array. One by one I kept adding elements, at each step the statement executing successfully, until all of the elements were back in the array.


Just in case it was not apparent in the narrative above, at no point was I able to identify the specific element on which the parser was choking. Even now I can compare the functional version of the @new array with the commented-out previous version and see no difference. Sometimes this is just the way things are. Perhaps I inwittingly removed a space from the string of elements as I was copying them back and forth. In any event, the end result is an appropriately-constructed and functional statement.


Another problem left the following line in the error.log.

DBD::Pg::st execute failed: ERROR:  value too long for type character(10) 
	at /home/www/bb_lib/BB_APP_INTERFACE.pm line 1023, <FH> line 11.
This was a little puzzling, inasmuch as I have not yet been consistently entering anything into any column other than team_name and the problem persisted even after I had changed the lengths of the two columns, fax and zip_post, that had been ten characters in length to fifteen characters. The source of this one was a mis-specification of the order in which I had stored elements in the @update array, The error message here was a little misleading in that the problem it described was secondary to the actual source of the error, but such circumstances are frequent. The art of debugging is often a matter of moving in ever-widening circles from observable symptoms.


In the process of resolving the issues above, I noticed a couple of things that I wanted to deal with before moving on. As I mentioned earlier, the current version of the teams_players() subroutine displays all of the records in the team table, including the blank one that I inserted some time ago to lead make_hash() to create a hash that, when sorted in sel_box(), generates a select box with a blank record in the first position. This, of course, is intended to prevent the inadvertent storage of whatever key whose value is in the first position of the sorted hash. Obviously, I really do not want that row displayed. While operations intended for a row with a blank key are problematic (try deleting a record with a blank key, for example), this is a potential source of confusion for a naive user attempting to maintain the table. It is easy to envision someone deciding to use that record as the point at which to enter a new record. Fortunately, this is an easy fix; I simply added this phrase

where team_id <> '    '	 
to the sql select statement referenced by the $all_teams statement handle. The returned result set with therefore not include that record. Note that the where clause fits into the sql statement is between the select from and the order by clause. This is the defined order for sql statements; getting things out of order will result in a statement that does not work.


The second item is as much a cautionary tale as anything else. For those unaware, a cautionary tale is a story meant to illustrate the consequences of unwise action in some regard. The story of the three little pigs, for example, is a cautionary tale at one level describing the consequences of building flimsy housing and at a deeper level about the desireability of a solid structure for one's affairs. In this particular circumstance I have been lashing my back with thorny branches since I discovered this, but I do not know that I will ever be able to totally expunge my guilt <grin>. Before I reveal the nature of my sin, let me describe the situation.


As I worked with the processing of submitted changes to the team records I noticed that when I would select the team option from the league menu I would sometimes get a page with the list of teams duplicated, which looks like this.



After a little bit of messing around I realized that I had forgotten to initialize the @rows array at the beginning of the subroutine, and that as a result the array displayed can be the array initially created by a previous invocation of the script. In those circumstances the array holds two sets of records because they have been added in both the previous and current invocations. In the server environment this is something that would never occur with a pure cgi script, because the perl interpeter starts anew with each submission in that circumstance. This is much more akin to the manner in which a stand-alone perl application would react to a similar problem. In that sense, this reveals something about the nature of using a mod_perl-enabled apache server and about the nature of client-server computing in general.


More than a few generations of jargon have developed around these concepts, and as a result there are a great many individuals who make a living bandying about terms like application and presentation layers with little understanding of the reality that underlies the defined jargon. Plainly stated, recognition of the fact that there are people who exist primarily to churn jargon is an important step in learning. Not everyone whose work is published is worth paying attention to, and presuming that they are can lead one to confusion as one tries to make sense of something that does not have any. One should not be afraid to read something and think, "That just does not make a whole lot of sense". Some might say the same about this document. They would, of course, be wrong.


It has been a while since I last unleashed a diatribe; that felt good<grin>. To return to the subject at hand, this is a good illustration of the fact that in the context of a mod_perl application there is an application resident in memory at the other end of the wire, and that it is possible to generate errors by not protecting against persistent structures. It would be a worthwhile exercise to the put the application in the state that will generate these errors by commenting out the lines initializing @rows and $k and the line "use strict;" at the beginning of the module. (I will explain the relevance of that shortly.) Step and start the server, and play around for a bit. Click the team option, generating a table of teams, then click on "Game Maintenance" and click again on "Team Maintenance". The double list of teams will probably be generated as above. Indeed, if quickly done, a repetition lf the process will lead to a triple listing. (This is not predictable, garbage collection on the server can lead to the initialization of a fresh copy of the array.) Submission of one of these pages would not lead to the creation of duplicate records, but would simply update each record multiple times. While from that standpoint no damage would be done, the operation of the system as a whole is based on similar structures and in other contexts similar problems could result in errors or simply render some dialogs unwieldy. I want to make sure that I do not have to worry about that.


The tool employed to guard against such problems is the strict pragma, which will simply not allow the oversights that create them to be made. I discussed this way back when, but when I started to create BB_APP_INTERFACE I simply forgot to turn it on. (I know, I know ... but I have already done my penance. Besides, this gives me a good illustration, right? I guess in a way I should be gratified that I have not run into problems previously, that suggests that I have generally been effective at scoping variables and keeping things compartmentalized.) As I said when I discussed the strict pragma in that earlier chapter, the standard manner in which to emply the strict pragma is to include "use strict;" in the group of lines at the beginning of the module. Note, however, that I have put that statement at the very end of that block.

package BB_APP_INTERFACE;


use Exporter;
@ISA = ('Exporter');
@EXPORT = ('store','get_form','get_session','construct_menu','sel_box','edit_choices','preferences','edit_page','edit_game_proc',
				'calendar_month','sched_edit','edit_schedule_proc','edit_table','sched_edit_2','teams_players');
use CGI qw(:all);
use FileHandle;
use FileHandle::Deluxe qw (:all);
use lib '/home/www/bb_lib';
use BB_UTIL;
use Date::Calc qw(:all);
use strict;
If I were to put it into the first line of this block I would have been forced to scope the @ISA and @EXPORT arrays, which would have interfered with the operation of the exporter module and rendered the subroutines that I am exporting unavailable to the rest of the system.


Once the strict pragma is enabled, checking the syntax of the module (i.e., "perl -c BB_APP_INTERFACE.pm") or trying to load the server will generate a set of error messages identifying items that have not been properly declared. For example, now that I have enabled the strict pragma, if I were to check the syntax of the module with the line in teams_players() that declares @rows commented out, I would get the following error messages:

Global symbol "@rows" requires explicit package name at BB_APP_INTERFACE.pm line 1424.
Global symbol "@rows" requires explicit package name at BB_APP_INTERFACE.pm line 1429.
BB_APP_INTERFACE.pm had compilation errors.
When I did this the first time with BB_APP_INTERFACE I had quite a smattering of error messages, because when a given variable is not initialized an error will be returned for each of the times that variable is used within the scope. It is therefore not unusual to have six or eight error messages associated with a single unscoped variable. I had perhaps two or three subroutines in the module in which I had neglected to declare one or more variables, with a total of maybe six or seven variables affected. (I will leave it to the reader to look through the copy of BB_APP_INTERFACE.pm to spot the errors, or to experiment with syntax checking on the module. That would be good practice.) Fixing these is obviously not difficult, being generally just a matter of inserting a line scoping the variable, but it is generally a good idea to at most fix the variables within one given subroutine at any given time, testing the components of the system that use that subroutine before moving on to the next. Every now and then there may be an unanticipated interfaction of some sort that has to be resolved, and it helps if the detective work is localized to a well-defined segment of the code,


As an aside, I noticed as I was testing the results of these declaration that two of the select box widgets on the game entry form, event and participant, were displaying a value rather than what should have been the default blank entry. Investigating further I saw that again each of the items in those two select boxes were marked as selected. Selecting the "view source" option in the browser, I saved the generated html, renamed it with a .html extension, and opened it in arachnophilia. I did that because arachnophilia has a "beautify html" option under tools, which structures the html, rendering it more legible. Once beautified, the pertinent select boxes looked like this example:

	Event Code:
							<select name=ec size=1>
								<option selected value=    >
								                              
								</option>
								<option selected value=03  >
								fielded                       
								</option>
								<option selected value=12  >
								pick-off (1st)                
								</option>
								<option selected value=13  >
								pick-off (2nd)                
								</option>
								<option selected value=14  >
								pick-off (3rd)                
								</option>
								<option selected value=01  >
								pitch                         
								</option>
								<option selected value=02  >
								swing                         
								</option>
								<option selected value=04  >
								throw                         
								</option>
							</select>

The two white spaces after the code value are readily apparent, and represent the source of the successful pattern match in sel_box() that flagged each line as selected, as I was passing two white spaces to the subroutine as the default pattern. Looking at the database structure, I realized that at some point I had widened the event and participants tables' code fields without making a corresponding change in the width of the values stored in the columns. Hence the whitespace trailing the values retrieved from the tables to create the hashes used in the creation of the select boxes. As always there are a number of ways in which I could handle this. One approach would be to anchor the pattern to the beginning of the string, as in
if ($v !~/^$default/)
In the end, however, I felt mote comfortable with reconciling the table widths with the widths of the values stored within. The set of database creation scripts called by bbdb_recreate.sh in the samples file for this chapter will recreate the table structures appropriately.


All that remains at this point is to associate the appropriate action with the form generated on the feedback page and label the submit button. To that end I have added these two lines to the schedule condition in the first switch block in the subroutine

			$form_action="<form action='/bb_app/league/la-sched' method='post'>";
			$submit_value=qq/Re-Display_Edited_Month/;
and these to the team condition
			$form_action="<form action='/bb_app/league/la-team' method='post'>";
			$submit_value=qq/Re-Display_Teams/;
Note the slightly different quoting method used here. Rather than manually quoting the strings, qq// for double-quote and q// for single quote will insert the quotes appropriately. Note also that the string for the submit value now has an underscore in the place of whitespace. Any string stored to a scalar will of course be subject to the same constraints in the manner in which it is read by perl mentioned earlier. The form at the bottom of the page is now started with
	$$r->print($form_action);
and the last two lines before the end of the form are
	$$r->print("<input type='hidden' name='month' value=$$params{'month'}[0]>
					<input type='hidden' name='year' value=$$params{'year'}[0]>") if $schedule;
	$$r->print("<br><input type='submit' value=$submit_value></center>");
The forms generated under either of the two contexts will now re-display the page previously edited when the submit button is clicked.


In the next chapter I will implement a player management scheme using this framework




Next: Managing Player Records