NAME
Win32::ASP::DBRecord - an abstract parent class for representing database records
SYNOPSIS
DESCRIPTION
The main purpose of Win32::ASP::DBRecord
is to be subclassed. It implements a generic set of default behavior for the purpose of reading a record from a table, displaying that record in HTML, allowing edits to it, and writing that record back to the table. It relies heavily upon Win32::ASP::Field objects, which are used to provide an object-oriented interface to the most important class data for a Win32::ASP::DBRecord
subclass - the fields possessed by the record represented by the class.
Internal Data Structure
The internal data structure of a instance of Win32::ASP::DBRecord
consists of the following elements, all optional:
- orig
-
This is a reference to a hash indexed on field names and storing data read from the database.
- edit
-
This is a reference to a hash indexed on field names and storing the currently modified data.
- child_dbgroup
-
There can be any number of child groups and these are stored at the root of the
Win32::ASP::DBRecord
object, not withinorig
oredit
. See_CHILDREN
for more information.
Class Methods
Class methods were used to implement access to class properties. Since Perl doesn't enforce a distinction between class and instance methods, these methods can be called on both class names and on instances of the class, which ends up being incredibly useful. I strongly recommend against ever calling these methods using subroutine notation (i.e. &_DB
or &_PRIMARY_KEY
). Perl methods execute in the namespace in which they were defined, which means that if you further subclass and define a new implementation of those methods, any methods you don't override that were in the parent class will call the parent class's versions of those methods. That's bad. Always call these methods with the arrow notation and you'll be safe.
Mandatory Class Methods
These class methods will be overridden in every child class.
- _DB
-
The
_DB
method should return theWin32::ASP::DB
(or subclass there-of) object that is used for database access. A frequent implementation looks like this:sub _DB { return $main::TheDB; }
- _FRIENDLY
-
The
_FRIENDLY
method should return a friendly name expressing what sorts of things these records are. This friendly name may get used in certain error messages (in particular,Win32::ASP::Error::Field::group_wrapper
). For instance, the_FRIENDLY
method for line items on an invoice might return "Line Item". An error message could then say, "There was an error in Line Item 4. The error was . . ." - _READ_SRC
-
The
_READ_SRC
method should return the name of the table or view that should be used to read records from the database. Frequently a view will be defined on the SQL Server to include information from various lookup tables. - _WRITE_SRC
-
The
_WRITE_SRC
method should return the name of the table that should be used to write records to the database. - _PRIMARY_KEY
-
The
_PRIMARY_KEY
method should return a list of the field names in the Primary Key for the table. Of note, this returns a list, not a reference to an array. The order that the fields are in_PRIMARY_KEY
is also the order in which the values will be specified for identifying records for reading them from the database. - _FIELDS
-
The
_FIELDS
method should return a reference to a hash ofWin32::ASP::Field
objects, indexed on the field names. Of note, for performance reasons the method is usually implemented like so:sub _FIELDS { return $MyStuff::MyRecord::fields; } $MyStuff::MyRecord::fields = { Win32::ASP::Field->new( name => 'RecordID', sec => 'ro', type => 'int', desc => 'Record ID', ), Win32::ASP::Field->new( name => 'RecordRemarks', sec => 'rw', type => 'text', ), };
Optional Class Methods
These class methods can be overriden in a child class.
- _ACTIONS
-
The
_ACTIONS
method should return a reference to a hash ofWin32::ASP::Action
objects, indexed on the action names. Actions are used to implement things that users do to records. For instance, a user might want to Edit a record. Some users might not have permissions to edit some records though, and so it makes sense to implement an object that is responsible for determining whether a given user is able to execute a given action in a given circumstance. The action is also responsible for displaying the appropriate HTML for a link that implements the action, knowing how to warn a user before the action is carried out, etc. For more information, see theWin32::ASP::Action
class and its various sub-classes.Of note, for performance reasons the method is usually implemented like so:
sub _ACTIONS { return $MyStuff::MyRecord::actions; } $MyStuff::MyRecord::actions = { Win32::ASP::Action::Edit->new, Win32::ASP::Action::Delete->new, Win32::ASP::Action->new( name => 'cancel', label => 'Cancel', . . . ), };
- _CHILDREN
-
Some records quite logically have child records. For instance, a Purchase Order generally has a number of line-items on it, and this are usually implemented using a table that is 1:M linked to the Purchase Order table. Within
Win32::ASP::DBRecord
class objects, this is implemented through a reference to aWin32::ASP::DBRecordGroup
class object that contains the child records.The implementation normally looks something like this:
sub _CHILDREN { return $MyStuff::MyRecord::children; } $MyStuff::MyRecord::children = { child_records => { type => 'MyStuff::MyChildRecordGroup', pkext => 'ChildID', }, };
The implication of the above is that
MyStuff::MyRecord
objects have a group of associatedMyStuff::MyChildRecord
objects, which are accessed through aMyStuff::MyChildRecordGroup
object. The reference to that object will be stored in$self->{child_records}
. The primary key of theMyStuff::MyChildRecord
objects will be the primary key for theMyStuff::MyRecord
objects plus the added field 'ChildID
'. The index on the hash is referred to hereafter as the 'child group name'.
Class Methods you probably won't override
There is only one of these.
- ADD_FIELDS
-
This method is called on the class in order to add new fields. This is usually used by
Win32::ASP::DBRecordGroup
objects to addWin32::ASP::Field::dispmeta
objects to the underlyingWin32::ASP::DBRecord
object.Win32::ASP::Field::dispmeta
objects are frequently used to display more than one field in a column when displaying a table of records (i.e. one field above the other).
Instance Methods
new
This is a basic new
method. Simply creates an anonymous hash and returns a reference. The new
method is not responsible for reading data or anything else. Just creating a new record object. You will probably not need to override this method.
init
This is used for initializing new records prior to being edited. The code in ASP land for throwing up the edit screen when creating a new record looks something like this:
use MyStuff::MyRecord;
$record = MyStuff::MyRecord->new;
$record->init;
$record->edit;
$data = 'edit';
$viewtype = 'edit';
This is then followed by the <FORM> section.
Note that init
modifies orig
, not edit
. Once orig
is modified, the edit
method is used to place the record in edit
mode.
read
The read
method is used, coincidentally, to read a record from the database. It should be passed an array comprised of the primary key values for the record desired.
The read
method is responsible for reading all appropriate values for the record, and for reading any child records for which the child group name shows up in $self
. The implications of this are important for providing appropriate behavior when update
is called.
The actual reading in of data from the ADO Recordset object is implemented by _read
. This is done so that Win32::ASP::DBRecordGroup
object can execute a query and then make calls to _read
for each record returned.
_read
The _read
method is responsible for reading the data from the ADO Recordset object ($result
) and entering it into the object. It does this by looping over the fields in _FIELDS
and calling read
on each of them with the appropriate parameters. Note that _read
accepts the optional parameter $columns
and passes this along in the call to read
on the Win32::ASP::Field
objects. This is to minimize unneeded value retrieval calls when Win32::ASP::DBRecordGroup
objects are only interested in a few fields. If $columns
is a reference to a hash, it will be interpreted as a list of the fieldnames of note. However, to allow for more flexibility in implementation, the decision as to whether or not the field will actually be read is still left up to the Win32::ASP::Field
object.
In addition, _read
is responsible for calling can_view
on the resultant record object to see whether the user is allowed to view this record. If can_view
returns false, _read
throws a Win32::ASP::Error::DBRecord::no_permission
exception
read_deep
Since the read
method is responsible for reading in all child records for which there is an entry in $self
, the read_deep
method simply creates an entry in the $self
hash for each key in the hash returned from _CHILDREN
.
post
The post
method takes data returned from a POST action and enters it into the Win32::ASP::DBRecord
object. Of note, post
takes a $row
as a parameter. This is used to identify which row of a table is of interest when being used for editing Win32::ASP::DBRecordGroup
objects.
The method simply calls post
on each of the Win32::ASP::Field
objects.
It also posts the data for all of the child records. The presumption is that if the records are really child records, one would generally edit the whole mess at one time and that they will then want to be posted. So it creates new child objects of the appropriate Win32::ASP::DBRecordGroup
classes and calls post
on them.
insert
The insert
method is responsible for taking the data and writing it to the database. If there are child records associated with object, those are written as well. Everything is wrapped in a transaction so that a failure to write child records for any reason will roll back the transaction.
The insert
method is passed a list of fields that should always be written. By default, the insert
method will only write values that are considered editable (as determined by calling can_edit
on the field object) <Bor> that show up in the passed list of fields. This enables one to define certain fields as read only, but still modify them within the context of actions or other code. Also, values are only written if they are defined in the $self->{edit}
hash. It is generally considered poor form to write NULL values to the database (especially in SQL Server 6.5 as this results in a 2K page being allocated for NULL text objects:).
The values are prepared for inserting by calling as_write_pair
on the Win32::ASP::Field
objects. The array of write pairs is then passed to the insert
method on the Win32::DB
object. The return from that call is the ADO Recordset object, which is then passed to set_inserted
so that auto generated Primary Key values can be retrieved
It then deals with the child record groups as needed. The defined objects have set_prop
used to propagate the primary key values onto the child objects. The insert
method can then be called to insert the entire group.
The insert
method returns a list of all write pairs that were inserted. This so that implementations that override insert
can make use of that information (this is most commonly done for logging purposes - other records are inserted into logging tables to indicate who did what when, and having insert
return the information makes that much easier.).
set_inserted
This method is responsible for retrieving the Primary Key values on newly inserted records. Most useful when one of those Primary Key values is an autonumber field.
update
This is the single largest, ugliest morass of code in Win32::ASP::DBRecord
. Yeach. Think of it as a slightly uglier insert
, though, and it's a little easier to understand.
First we start a transaction and call can_update
. The can_update
method will call read
in turn (no way to know if we can update a record if we don't know what was in it).
If can_update
returns false, we throw a Win32::ASP::Error::DBRecord::no_permission
exception and get out of here. Otherwise, we procede to call verify_record
and verify_timestamp
. If neither of those throw exceptions, we continue on.
The method then creates <C$constraint>, a SQL WHERE
condition suitable for indentifying the record of interest based on the Primary Key.
It then starts building a list of write pairs. It also adds those pairs to @retvals
, which will contain a list of fields, new values, and old values for any field that changed. Note that we only update fields for which can_edit
returns true and that have changed, or that are mentioned in @ext_fields
, the passed parameter list. Fields updated as a result of being in @ext_fields
are not mentioned in the list of changed fields that is returned.
BUGS
- Triple level children
-
The implementation of child records does not deal properly with situation in which the child records have children themselves. This issue will be resolved when I have time.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 309:
=back without =over