Win32::ASP::Field - an abstract parent class for representing database fields, used by Win32::ASP::DBRecord |
Win32::ASP::Field - an abstract parent class for representing database fields, used by Win32::ASP::DBRecord
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', ), );
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:
SUPER::
, call _method
on the object.
If the property does not exist, it attempts to call _method
on the object. Thus, calling
read
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
, the AUTOLOAD
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.
Except for new
, which is discussed here, the majority of these are farther down under
INTERNALS.
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.
$record
in
question.
'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 when seq
is called in _read
. You may wish to return
'ro'
if in doubt.
name
.
TEXT
and TEXTAREA
form fields to define their width.
TEXTAREA
form field.
formname
. If not specified, the default method will return name
.
as_write
. It can sometimes be very handy to use a subroutine for
writename
. As a subroutine, it gets passed $value
. If it needs the whole record to make
its decision, you will need to intercept the as_write_pair
method.
Say, for instance, that you have a logging record with a SmallValue
field that is a 50
character varchar
and a LargeValue
field that is a text
field. The idea is that for
short strings you won't incur much cost from the LargeValue
field because uninitialized text
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 unused varchar
for the long strings is small contrasted
with the savings by not using the text
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 of as_write
is required because the formatting for
varchar
and text
fields is the same.
as_html_edit_rw
the intention to use as_html_options
.
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.
writename
, as_write
, and option_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
and option_list
methods are implemented to help minimize SQL
traffic. The first call to the option_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 the AUTOLOAD
method
in Class::SelfMethods
. See the entry for group
for a discussion of the behavior for
as_write
.
Win32::ASP::DBRecord
subclass to which this field belongs. This will be used later
to access the _FIELDS
hash and the _DB
object.
writename
.
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 to as_write
will lookup all the codes and store them in a hash for
further reference. This will reduce SQL traffic in situations where an Win32::ASP::DBRecord
object is
used within a Win32::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.
This is where internal methods are discussed with an eye towards over-riding them if need be.
These are quick little methods to provided standardized ways of checking certain boolean ``properties''
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
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
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 full option_list
in order to test for it. If you implement a subclass that
implements option_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.
These methods are used to load a record with a given field.
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
, the Win32::ASP::DBRecord
that will receive the data,
and $results
, the ADO Recordset object containing the data, the read
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 the
Win32::ASP::DBRecord
object is part of a Win32::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 the writename
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
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.
These methods are used to format a given value as HTML.
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 calls
as_html_edit_rw
or as_html_options
based on is_option_list
. If it isn't editable but
$viewtype
is 'edit
', it calls as_html_edit_ro
. Finally, if we aren't in 'edit
' mode,
it calls as_html_view
.
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 in
Win32::ASP::Field::timestamp
to return nothing (timestamp
is not the same as datetime
).
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 a HIDDEN
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 (since
timestamp
values are binary and thus not healthy HTML).
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 appropriate TEXT
INPUT
field. Note the
call to $self-
as_html_mouseover>, which returns the appropriate parameters to implement the
help
support.
The method is over-ridden by Win32::ASP::Field::bit
to display a Yes/No radio pair and by
Win32::ASP::Field::text
to display a TEXTAREA
.
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 a SELECT
structure with the appropriate OPTION
entries. It specified SELECTED
for the appropriate one based on $value
.
as_html_mouseover
method takes two parameters, $record
and $data
, and returns the
appropriate string with onMouseOver
and onMouseOut
method for inclusion into HTML.
The default implementation, _as_html_mouseover
, ignores the passed parameters and builds
JavaScript for setting window.status
to $self->help
.
Values need to be formatted as legal SQL for the purposes of being included in query strings.
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 the
check_value_write
method discussed later. If the check fails, check_value should throw an
error. Ideally, the error will either be of class Win32::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 in Win32::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), by Win32::ASP::Field::datetime
to use
Win32::ASP::Field::_clean_datetime
which use OLE to verify a datetime value, by
Win32::ASP::Field::int
to verify that the value is an integer, and by
Win32::ASP::Field::varchar
to verify that it doesn't exceed the maximum length.
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 in Win32::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 of Win32::ASP::Field::dispmeta
and Win32::ASP::Field::timestamp
, which are never
used to query or write to the database.
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
method gets passed $value
and returns the value that will be paired with
writename
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.
$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 calls check_value
on the appropriate $value
. If the check fails
for whatever reason, check_value_write
should throw an exception.
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 call check_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 and value
should supply the properly formatted data for
inclusion into SQL. Note that if, for some reason, the functionality usually supplied by
writename
requires knowledge of the entire record, that functionality should be subsumed into
as_write_pair
.
Win32::ASP::Field - an abstract parent class for representing database fields, used by Win32::ASP::DBRecord |