The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Win32::ASP::DBRecord - an abstract parent class for representing database records

SYNOPSIS

DESCRIPTION

The main purpose of Win32::ASP::DBRecordis 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 within orig or edit. 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 the Win32::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 of Win32::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 of Win32::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 the Win32::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 a Win32::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 associated MyStuff::MyChildRecord objects, which are accessed through a MyStuff::MyChildRecordGroup object. The reference to that object will be stored in $self->{child_records}. The primary key of the MyStuff::MyChildRecord objects will be the primary key for the MyStuff::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 add Win32::ASP::Field::dispmeta objects to the underlying Win32::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-&gt;{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