NAME
Win32::ASP::Field - an abstract parent class for representing database fields, used by Win32::ASP::DBRecord
SYNOPSIS
use Win32::ASP::Field;
%hash = (
Win32::ASP::Field->new(
name => 'RecordID',
sec => 'ro',
type => 'int',
desc => 'Record ID',
),
Win32::ASP::Field->new(
name => 'SemiSecureField',
sec => sub {
my $self = shift;
my($record) = @_;
return $record->role eq 'admin' ? 'rw' : 'ro';
},
type => 'varchar',
desc => 'Semi Secure Field',
),
Win32::ASP::Field->new(
name => 'Remarks',
sec => 'rw',
type => 'text',
),
);
DESCRIPTION
Background
Field objects are very strange Perl objects. Perl is class based, not prototype based. Unfortunately, unless you want to create a separate class for every mildly wierd field in your database, a class based system is sub-optimal for our purposes. To get around this I implemented a "weak" form of a prototype based language.
The major parent is Class::SelfMethods
. It provides an AUTOLOAD
that implements the desired behavior. In a nutshell, when asked to resolve a method it does the following:
First it checks for whether the object has a property with that name. If it does and the property is not a code reference, it returns the value.
If the property is a code reference, it evaluates the code reference on the object with the passed parameters. This means that you can define "instance" (not class) methods by placing anonymous subroutines in the instance. These override the class method. If you need to call the equivalent of
SUPER::
, call_method
on the object.If the property does not exist, it attempts to call
_method
on the object. Thus, callingread
on an instance calls the_read
method in the class definition if there is no matching property. If the_read
method exists,AUTOLOAD
will not get called again. On the other hand if it does not exist, rather than call__read
, theAUTOLOAD
subroutine will return empty-handed. This way, if the desired property is not defined for the object,undef
will be the default behavior.
It is important to understand the above "hierarchy of behavior" if you are to make full use of the customizability of Field objects. In a nutshell, when creating new proper classes all methods should be defined with a leading underscore, but called without the leading underscore, so that they can be over-ridden as needed. One should never directly access the hash $self
, but always let AUTOLOAD
access it by calling the method with that name. That way instance variables can be implemented as subroutines if need be. It also makes it easy to provide "default" behavior by implementing a method. The only time a method should be called with a leading underscore is when an instance-defined method needs to call the method it is over-riding.
Methods
Except for new
, which is discussed here, the majority of these are farther down under "INTERNALS".
new
The new
method for Win32::ASP::Field
is rather strange. It returns two values - the name along with the Win32::ASP::Field
object. This makes it much easier to create hashes of Win32::ASP::Field
objects. The parameters passed to the new
method should be the desired properties for the new object.
One oddity is that the type
property will be used to autoload Win32::ASP::Field::
type
and the returned object will be of that class. This makes it possible to create arbitrary Win32::ASP::Field
objects without an explicit list of use
statements at the top.
For an example of how to use new
, see the "SYNOPSIS" at the top of the POD.
For a discussion of how new
treats passed parameters that have a name that starts with an underscore, see Meta properties.
Properties
Required
- name
-
This is the name of the field. Unlike the other properties, it is not passed the
$record
in question. - sec
-
This specified whether the field is read-write (
'rw'
) or read-only ('ro'
). Note, you can implement this as a subroutine and it gets passed$record
. If it is not implemented or returns a value not equal to one of the above, it is presumed that the value is not accessible. Note that$record
may not be fully formed whenseq
is called in_read
. You may wish to return'ro'
if in doubt.
Optional
- reqd
-
If this returns true than the field is required to be filled out when writing.
- desc
-
This is the friendly description for the object. This gets used for column headings in tables. If not specified, it defaults to
name
. - help
-
This is the text that displays in the browser status area when the mouse is placed over the edit area in edit mode
- size
-
This is used for
TEXT
andTEXTAREA
form fields to define their width. - maxl
-
This is used to specify the maximum length of a varchar field.
- textrows
-
This is used to specify the number of rows for a
TEXTAREA
form field. - formname
-
This is used to deal with situations where a field in a child record has the same name as a field in a parent record. This would, of course, complicate the resultant HTML form. To deal with this situation, specify
formname
. If not specified, the default method will returnname
. - writename
-
This is used to indicate the actual SQL field used for recording. It is frequently used in conjunction with
as_write
. It can sometimes be very handy to use a subroutine forwritename
. As a subroutine, it gets passed$value
. If it needs the whole record to make its decision, you will need to intercept theas_write_pair
method.Say, for instance, that you have a logging record with a
SmallValue
field that is a 50 charactervarchar
and aLargeValue
field that is atext
field. The idea is that for short strings you won't incur much cost from theLargeValue
field because uninitializedtext
fields don't create a 2K page. If the string is longer, however, you want to write to the LargeValue field. If the percentage of short strings is 50%, the solution would save ~49.7% on space requirements. The penalty of the unusedvarchar
for the long strings is small contrasted with the savings by not using thetext
field on the short ones.In that situation, one might implement
writename
like this:writename => sub { my $self = shift; my($value) = @_; return length($value) > 50 ? 'LargeValue' : 'SmallValue'; }
The discussion of
read
includes an appropriate instance level method to round out this demonstration. No implementation ofas_write
is required because the formatting forvarchar
andtext
fields is the same. - option_list
-
This should return an anonymous array of options that will be provided to the user when editing the field. Its presence indicates to
as_html_edit_rw
the intention to useas_html_options
.
Meta
Meta properties are a funky way of executing additional code at the time of object creation. The new method accepts a parameter list and returns two values - the name of the field and the field object itself. The advantage of this is that it makes creating a hash of field object much easier. On the other hand, it requires some excessively fancy notation to make method calls on the newly created object while in the hash specifier. However, there's any easy way to indicate when you want a parameter to be a method call. Since parameters don't start with underscores and all actual implementations in class code do, it makes sense to start meta properties with an underscore. The new method simply scans the list of parameters for those that start with an underscore and strips them out of the parameter hash for later use. The value of the parameter should be an anonymous array of parameters to the method.
Typical use of meta properties is to provide code for creating commonly used instance methods.
- _standard_option_list
-
This meta property sets up
writename
,as_write
, andoption_list
for use with a fairly standard option list that uses a "hidden" code field and a lookup table that has friendly descriptions. Example usage might look like so:_standard_option_list => [ class => 'MyDatabase::MyRecord', writename => 'LookupCode', table => 'LookupCodes', field => 'LookupCode', desc => 'Description' ],
Note that although the method is expecting a hash of properties, the parameter list is stored in an anonymous array when passed in during the new method.
Of note, the
as_write
andoption_list
methods are implemented to help minimize SQL traffic. The first call to theoption_list
method will result in setting$self->{option_list}
to a reference to the anonymous array before returning that array. Further calls will automatically return that array based on the behavior of theAUTOLOAD
method inClass::SelfMethods
. See the entry forgroup
for a discussion of the behavior foras_write
.- class
-
This specifies the
Win32::ASP::DBRecord
subclass to which this field belongs. This will be used later to access the_FIELDS
hash and the_DB
object. - writename
-
This specifies the field within the record object that will be written.
- table
-
This specifies the name of the table that contains the list of codes and the friendly descriptions.
- field
-
This specifies the name of the field within that table that contains the code. Frequently, but not always, this will be the same as
writename
. - desc
-
This specifies the name of the description field in the lookup table.
- group
-
This specifies whether there are likely to be multiple calls to
as_write
. If not present or set to a false value,as_write
will only lookup the passed value. If set to a true value, the first call toas_write
will lookup all the codes and store them in a hash for further reference. This will reduce SQL traffic in situations where anWin32::ASP::DBRecord
object is used within aWin32::ASP::DBRecordGroup
for editing records. Unfortunately, the code isn't smart enough to know whether it is being used in a group or on its own, so you have to hard code it. On the other hand, if you need that level of flexibility, you can roll your own methods.
INTERNALS
This is where internal methods are discussed with an eye towards over-riding them if need be.
Checkers
These are quick little methods to provided standardized ways of checking certain boolean "properties"
- can_view
-
The
can_view
method is used to determine if someone has view privileges on a given field. The default implementation,_can_view
, tests$self->sec($record)
for equivalence with 'ro
' or 'rw
'.Implementations can expect the $record as a parameter and should return 1 or 0 as appropriate.
- can_edit
-
The
can_edit
method is used to determine if someone has edit privileges on a given field. The default implementation,_can_edit
, tests$self->sec($record)
for equivalence with 'rw
'.Implementations can expect the $record as a parameter and should return 1 or 0 as appropriate.
- is_option_list
-
The
is_option_list
method is used to determine if a field should be displayed using an option list. The default implementation,_is_option_list
, tests for the existence of$self->{option_list}
. This is technically verboten, but it's a performance improvement over returning the fulloption_list
in order to test for it. If you implement a subclass that implementsoption_list
, you should also implement_is_option_list
.Implementations can expect $record and $data as a parameter and should return 1 or 0 as appropriate.
Loaders
These methods are used to load a record with a given field.
- read
-
The
read
method is used to read a specific field out of$results
into$record
. The default implementation,_read
, first calls$self->can_view
and then retrieves the appropriate value (if present) from the results set and places it in$record->{orig}
as appropriate.In addition to the parameters
$record
, theWin32::ASP::DBRecord
that will receive the data, and$results
, the ADO Recordset object containing the data, theread
method is passed the parameter$columns
. If$columns
contains a reference to a hash and <$self->name> doesn't return a true value, the data should not be read. This improves performance when theWin32::ASP::DBRecord
object is part of aWin32::ASP::DBRecordGroup
that is being used to retrieve data from a query where only some of the fields will be displayed.The properly written
read
for thewritename
function displayed long ago would be:read => sub { my $self = shift; my($record, $results, $columns) = @_; my $name = $self->name; ref($columns) and !$columns->{$name} and return; $self->can_view($record) or return; $record->{orig}->{$name} = undef; $results->Fields->Item('SmallValue') and $record->{orig}->{$name} = $results->Fields->Item('SmallValue')->Value; if ($record->{orig}->{$name} eq '') { $results->Fields->Item('LargeValue') and $record->{orig}->{$name} = $results->Fields->Item('LargeValue')->Value; } },
- post
-
The
post
method is used to read a specific field into$results
from the POST data. It also takes$row
as a parameter. If$row
is defined, it presumes that it is dealing with a DBRecord that is a member of a DBRecordGroup and should retrieve the appropriately indexed value from the multi-valued POST data. If it is not defined, it presumes that it is dealing with single-valued POST data.It assigns the value into
$record->{edit}
as appropriate. It also tests for whether the POST data contains any non-whitespace characters and assigns undef if it does not.
HTML Formatters
These methods are used to format a given value as HTML.
- as_html
-
The
as_html
method is the accepted external interface for displaying a field in HTML. It takes three parameters,$record
,$data
, and$viewtype
, and returns the appropriate HTML.The default implementation,
_as_html
, first checks for whether the$record
is viewable. If it is not, it simply returns. It then checks to see if$viewtype
is 'edit
'. If it is, it calls$self->can_edit($record)
to determine if the field is editable. If it is, it callsas_html_edit_rw
oras_html_options
based onis_option_list
. If it isn't editable but$viewtype
is 'edit
', it callsas_html_edit_ro
. Finally, if we aren't in 'edit
' mode, it callsas_html_view
. - as_html_view
-
The
as_html_view
method takes two parameters,$record
and$data
, and returns the appropriate HTML.The default implementation,
_as_html_view
, first extracts$value
from$record
using$data
and$self->name
. If it is defined, it returns it, otherwise it returns '
'. It runs the string through HTMLEncode to enable it to pass HTML meta-characters.This is over-ridden in
Win32::ASP::Field::bit
to return 'Yes
' or 'No
' and inWin32::ASP::Field::timestamp
to return nothing (timestamp
is not the same asdatetime
). - as_html_edit_ro
-
The
as_html_edit_ro
method takes two parameters,$record
and$data
, and returns the appropriate HTML.The default implementation,
_as_html_edit_ro
, first extracts$value
from$record
using$data
and$self->name
. It concatenates aHIDDEN
INPUT
field with the results of$self->as_html_view($record, $data)
.This method is over-riden in
Win32::ASP::Field::timestamp
to encode$value
as hex (sincetimestamp
values are binary and thus not healthy HTML). - as_html_edit_rw
-
The
as_html_edit_rw
method takes two parameters,$record
and$data
, and returns the appropriate HTML.The default implementation,
_as_html_edit_rw
, first extracts$value
from$record
using$data
and$self->name
. It then creates an appropriateTEXT
INPUT
field. Note the call to$self-
as_html_mouseover>, which returns the appropriate parameters to implement thehelp
support.The method is over-ridden by
Win32::ASP::Field::bit
to display a Yes/No radio pair and byWin32::ASP::Field::text
to display aTEXTAREA
. - as_html_options
-
The
as_html_options
method takes two parameters,$record
and$data
, and returns the appropriate HTML.The default implementation,
_as_html_options
, first extracts$value
from$record
using$data
and$self->name
. It then loops over the values returned from$self->option_list
and creates aSELECT
structure with the appropriateOPTION
entries. It specifiedSELECTED
for the appropriate one based on$value
. - as_html_mouseover
-
The
as_html_mouseover
method takes two parameters,$record
and$data
, and returns the appropriate string withonMouseOver
andonMouseOut
method for inclusion into HTML.The default implementation,
_as_html_mouseover
, ignores the passed parameters and builds JavaScript for settingwindow.status
to$self->help
.
SQL Formatters
Values need to be formatted as legal SQL for the purposes of being included in query strings.
- check_value
-
The
check_value
method is responsible for field level checking of$value
. Note that this code does not have access to the entire record, and so record-based checking should be left to thecheck_value_write
method discussed later. If the check fails, check_value should throw an error. Ideally, the error will either be of classWin32::ASP::Error::Field::bad_value
or a subclass thereof. There should be no checking for "requiredness" at this level (simply because in many situations it wouldn't be called and so putting it here lends false hope). The default implementation inWin32::ASP::Field
does no checking what-so-ever and is merely provided as a prototype.The method is over-ridden by
Win32::ASP::Field::bit
to verify that the value is a 0 or 1 (bit fields never allow NULLs), byWin32::ASP::Field::datetime
to useWin32::ASP::Field::_clean_datetime
which use OLE to verify a datetime value, byWin32::ASP::Field::int
to verify that the value is an integer, and byWin32::ASP::Field::varchar
to verify that it doesn't exceed the maximum length. - as_sql
-
The
as_sql
method is responsible for formatting of$value
for inclusion in SQL. Since this code will be called during the query phase, it doesn't have access to an entire record. The default implementation inWin32::ASP::Field
does nothing at all and is merely provided as a prototype.The method is, therefore, implemented by almost every subclass of
Win32::ASP::Field
, with the exception ofWin32::ASP::Field::dispmeta
andWin32::ASP::Field::timestamp
, which are never used to query or write to the database.
Writing Formatters
The writing formatters are responsible for preparing the output for updating or inserting records. Some of these have access to the full $record
object, and others only have access to the $value
. In order to decentralize management of the constraint checking, it would be useful if some $record
object checking could be pushed out to the field objects. At the same time, there are situations where a fully formed $record
object is not available for field level checking. As a result, there is a profusion of the various formatters and checkers. Rather than discussing them in a top-down fashion, I will start from the bottom as things may make more sense that way.
- as_write
-
The
as_write
method gets passed$value
and returns the value that will be paired withwritename
for writing to the database. Note that it does not get passed the full record - otherwise it would be difficult to call as_write from an overridden as_write.For example, to implement
as_write
for looking up a value in a database (obviously just for demonstration purposes - normally you would use_standard_option_list
), one might use:as_write => sub { my $self = shift; my($value) = @_; my $results = MyDatabase::MyRecord->_DB->exec_sql(<<ENDSQL, error_no_records => 1); SELECT LookupCode FROM LookupCodes WHERE Description = '$value' ENDSQL return MyDatabase::MyRecord->_FIELDS->{$self->writename($value)}->as_write($results->Fields->('LookupCode')->Value); },
That last return line is rather ugly, so let me dissect it:
$self->writename
returns the fieldname to which the return value will actually get written.MyDatabase::MyRecord->_FIELDS
returns the hash of field objects for whatever class is involved.MyDatabase::MyRecord->_FIELDS->{$self->writename}
returns the actual field object of interest.as_write
is then called on that object with the value returned by looking up the appropriate result in the database.
The main reason for the last line is so that it will properly format the return value using whatever type of field the
writename
is. This shouldn't be an issue for common fields, but it could be for date/time values in some circumstances. - check_value_write
-
This is the first of the methods that have access to a full
$record
. It gets passed both$record
and$data
and as such can check a given field against other fields in the record. The default implementation callscheck_value
on the appropriate$value
. If the check fails for whatever reason,check_value_write
should throw an exception. - as_write_pair
-
The method
as_write_pair
is the accepted entry point for formatting a value for writing to the database. It accepts$record
and$data
, so it can callcheck_value_write
to perform record-dependent field validation. It returns a hash composed of two key/value pairs:field
should supply the fieldname to write to andvalue
should supply the properly formatted data for inclusion into SQL. Note that if, for some reason, the functionality usually supplied bywritename
requires knowledge of the entire record, that functionality should be subsumed intoas_write_pair
.