home *** CD-ROM | disk | FTP | other *** search
- ############################################################################
- #
- # Win32::ASP::DBRecord - an abstract parent class for representing database
- # records in the Win32-ASP-DB system
- #
- # Author: Toby Everett
- # Revision: 0.02
- # Last Change:
- ############################################################################
- # Copyright 1999, 2000 Toby Everett. All rights reserved.
- #
- # This file is distributed under the Artistic License. See
- # http://www.ActiveState.com/corporate/artistic_license.htm or
- # the license that comes with your perl distribution.
- #
- # For comments, questions, bugs or general interest, feel free to
- # contact Toby Everett at teverett@alascom.att.com
- ############################################################################
-
- use Win32::ASP::Action;
- use Win32::ASP::Field;
- use Error qw/:try/;
- use Win32::ASP::Error;
-
- package Win32::ASP::DBRecord;
-
- use strict;
-
- =head1 NAME
-
- Win32::ASP::DBRecord - an abstract parent class for representing database records
-
- =head1 SYNOPSIS
-
- =head1 DESCRIPTION
-
- The main purpose of C<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 C<Win32::ASP::DBRecord> subclass - the fields possessed by the record
- represented by the class.
-
- =head2 Internal Data Structure
-
- The internal data structure of a instance of C<Win32::ASP::DBRecord> consists of the following
- elements, all optional:
-
- =over 4
-
- =item orig
-
- This is a reference to a hash indexed on field names and storing data read from the database.
-
- =item edit
-
- This is a reference to a hash indexed on field names and storing the currently modified data.
-
- =item I<child_dbgroup>
-
- There can be any number of child groups and these are stored at the root of the
- C<Win32::ASP::DBRecord> object, not within C<orig> or C<edit>. See C<_CHILDREN> for more
- information.
-
- =back
-
- =head2 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. C<&_DB> or C<&_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.
-
- =head3 Mandatory Class Methods
-
- These class methods will be overridden in every child class.
-
- =over 4
-
- =item _DB
-
- The C<_DB> method should return the C<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;
- }
-
- =cut
-
- sub _DB {
- return $main::TheDB;
- }
-
- =item _FRIENDLY
-
- The C<_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,
- C<Win32::ASP::Error::Field::group_wrapper>). For instance, the C<_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 . . ."
-
- =cut
-
- sub _FRIENDLY {
- return 'MyRecord';
- }
-
- =item _READ_SRC
-
- The C<_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.
-
- =cut
-
- sub _READ_SRC {
- return 'MyView';
- }
-
- =item _WRITE_SRC
-
- The C<_WRITE_SRC> method should return the name of the table that should be used to write records
- to the database.
-
- =cut
-
- sub _WRITE_SRC {
- return 'MyTable';
- }
-
- =item _PRIMARY_KEY
-
- The C<_PRIMARY_KEY> method should return a list of the field names in the Primary Key for the
- table. Of note, this returns a B<list>, not a B<reference to an array>. The order that the
- fields are in C<_PRIMARY_KEY> is also the order in which the values will be specified for
- identifying records for reading them from the database.
-
- =cut
-
- sub _PRIMARY_KEY {
- return ('ID');
- }
-
- =item _FIELDS
-
- The C<_FIELDS> method should return a reference to a hash of C<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',
- ),
-
- };
-
- =cut
-
- sub _FIELDS {
- return $Win32::ASP::DBRecord::fields;
- }
-
- $Win32::ASP::DBRecord::fields = {
-
- };
-
-
-
- =back
-
- =head3 Optional Class Methods
-
- These class methods can be overriden in a child class.
-
- =over 4
-
- =item _ACTIONS
-
- The C<_ACTIONS> method should return a reference to a hash of C<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
- C<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',
- . . .
- ),
-
- };
-
- =cut
-
- sub _ACTIONS {
- return $Win32::ASP::DBRecord::actions;
- }
-
- $Win32::ASP::DBRecord::actions = {
-
- };
-
- =item _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 C<Win32::ASP::DBRecord> class objects, this is implemented
- through a reference to a C<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 C<MyStuff::MyRecord> objects have a group of associated
- C<MyStuff::MyChildRecord> objects, which are accessed through a C<MyStuff::MyChildRecordGroup>
- object. The reference to that object will be stored in C<$self->{child_records}>. The
- primary key of the C<MyStuff::MyChildRecord> objects will be the primary key for the
- C<MyStuff::MyRecord> objects plus the added field 'C<ChildID>'. The index on the hash is referred
- to hereafter as the 'child group name'.
-
- =cut
-
- sub _CHILDREN {
- return $Win32::ASP::DBRecord::children;
- }
-
- $Win32::ASP::DBRecord::children = {
-
- };
-
-
-
- =back
-
- =head3 Class Methods you probably won't override
-
- There is only one of these.
-
- =over 4
-
- =item ADD_FIELDS
-
- This method is called on the class in order to add new fields. This is usually used by
- C<Win32::ASP::DBRecordGroup> objects to add C<Win32::ASP::Field::dispmeta> objects to the
- underlying C<Win32::ASP::DBRecord> object. C<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).
-
- =cut
-
- sub ADD_FIELDS {
- my $class = shift;
- my(%fields) = @_;
-
- no strict;
- foreach my $i (keys %fields) {
- ${"${class}::fields"}->{$i} = $fields{$i};
- }
- }
-
-
-
- =back
-
- =back
-
- =head2 Instance Methods
-
- =head3 new
-
- This is a basic C<new> method. Simply creates an anonymous hash and returns a reference. The
- C<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.
-
- =cut
-
- sub new {
- my $class = shift;
-
- my $self = {
- };
- bless $self, $class;
- return $self;
- }
-
- =head3 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 C<init> modifies C<orig>, not C<edit>. Once C<orig> is modified, the C<edit> method is
- used to place the record in C<edit> mode.
-
- =cut
-
- sub init {
- my $self = shift;
-
- $self->{orig} = {};
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- $self->{$child} = $type->new;
- }
- }
-
- =head3 read
-
- The C<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 C<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 C<$self>. The implications
- of this are important for providing appropriate behavior when C<update> is called.
-
- The actual reading in of data from the ADO Recordset object is implemented by C<_read>. This
- is done so that C<Win32::ASP::DBRecordGroup> object can execute a query and then make calls to
- C<_read> for each record returned.
-
- =cut
-
- sub read {
- my $self = shift;
- my(@key_vals) = @_;
-
- exists ($self->{orig}) and return;
-
- my(%key_vals) = $self->clean_key_vals('edit', @key_vals);
- my $constraint = join(" AND\n ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($key_vals{$_})} $self->_PRIMARY_KEY)."\n";
- my $results = $self->_DB->exec_sql("SELECT * FROM ".$self->_READ_SRC."\nWHERE $constraint", error_no_records => 1);
-
- $self->_read($results);
-
-
- if (%{$self->_CHILDREN}) {
- foreach my $child (keys %{$self->_CHILDREN}) {
- if (exists $self->{$child}) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- unless (ref($self->{$child})) {
- $self->{$child} = $type->new;
- $self->{$child}->{parent} = $self;
- }
- $self->{$child}->query({%key_vals}, $pkext);
- }
- }
- }
- }
-
- =head3 _read
-
- The C<_read> method is responsible for reading the data from the ADO Recordset object (C<$result>)
- and entering it into the object. It does this by looping over the fields in C<_FIELDS> and calling
- C<read> on each of them with the appropriate parameters. Note that C<_read> accepts the optional
- parameter C<$columns> and passes this along in the call to C<read> on the C<Win32::ASP::Field>
- objects. This is to minimize unneeded value retrieval calls when C<Win32::ASP::DBRecordGroup>
- objects are only interested in a few fields. If C<$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 C<Win32::ASP::Field> object.
-
- In addition, C<_read> is responsible for calling C<can_view> on the resultant record object to see
- whether the user is allowed to view this record. If C<can_view> returns false, C<_read> throws a
- C<Win32::ASP::Error::DBRecord::no_permission> exception
-
- =cut
-
- sub _read {
- my $self = shift;
- my($results, $columns) = @_;
-
- foreach my $field (values %{$self->_FIELDS}) {
- $field->read($self, $results, $columns);
- }
-
- unless ($self->can_view) {
- my $identifier = join(", ", map {"$_ $self->{orig}->{$_}"} $self->_PRIMARY_KEY);
- $self->{orig} = undef;
- throw Win32::ASP::Error::DBRecord::no_permission (action => 'view', identifier => $identifier);
- }
- }
-
- =head3 read_deep
-
- Since the C<read> method is responsible for reading in all child records for which there is an
- entry in C<$self>, the C<read_deep> method simply creates an entry in the C<$self> hash for each
- key in the hash returned from C<_CHILDREN>.
-
- =cut
-
- sub read_deep {
- my $self = shift;
- my(@key_vals) = @_;
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- $self->{$child} = undef;
- }
- $self->read(@key_vals);
- }
-
- =head3 post
-
- The C<post> method takes data returned from a POST action and enters it into the
- C<Win32::ASP::DBRecord> object. Of note, C<post> takes a C<$row> as a parameter. This is used to
- identify which row of a table is of interest when being used for editing
- C<Win32::ASP::DBRecordGroup> objects.
-
- The method simply calls C<post> on each of the C<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
- C<Win32::ASP::DBRecordGroup> classes and calls C<post> on them.
-
- =cut
-
- sub post {
- my $self = shift;
- my($row) = @_;
-
- foreach my $field (values %{$self->_FIELDS}) {
- $field->post($self, $row);
- }
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- $self->{$child} = $type->new;
- $self->{$child}->{parent} = $self;
- $self->{$child}->post;
- }
- }
-
- =head3 insert
-
- The C<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 C<insert> method is passed a list of fields that should always be written. By default, the
- C<insert> method will only write values that are considered editable (as determined by calling
- C<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 C<$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 C<as_write_pair> on the C<Win32::ASP::Field>
- objects. The array of write pairs is then passed to the C<insert> method on the C<Win32::DB>
- object. The return from that call is the ADO Recordset object, which is then passed to
- C<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 C<set_prop> used
- to propagate the primary key values onto the child objects. The C<insert> method can then be
- called to insert the entire group.
-
- The C<insert> method returns a list of all write pairs that were inserted. This so that
- implementations that override C<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 C<insert> return the information makes that much easier.).
-
- =cut
-
- sub insert {
- my $self = shift;
- my(@ext_fields) = @_;
-
- my(@pairs);
- $self->_DB->begin_trans;
- {
- $self->verify_record;
-
- my(%ext_fields) = map {($_, 1)} @ext_fields;
- foreach my $field (values %{$self->_FIELDS}) {
- if (($ext_fields{$field->name} or $field->can_edit($self, 'edit')) && defined $self->{edit}->{$field->name}) {
- push(@pairs, $field->as_write_pair($self, 'edit'));
- }
- }
-
- $self->set_inserted($self->_DB->insert($self->_WRITE_SRC, @pairs));
-
- if (%{$self->_CHILDREN}) {
- my(%key_vals) = $self->clean_key_vals('edit');
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- if (ref($self->{$child})) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- foreach my $field (keys %key_vals) {
- $self->{$child}->set_prop($field, $key_vals{$field});
- }
- $self->{$child}->insert;
- }
- }
- }
- }
- $self->_DB->commit_trans;
-
- return (@pairs);
- }
-
- =head3 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.
-
- =cut
-
- sub set_inserted {
- my $self = shift;
- my($results) = @_;
-
- foreach my $i ($self->_PRIMARY_KEY) {
- $self->{edit}->{$i} = $results->Fields->Item($i)->Value;
- }
- }
-
- =head3 update
-
- This is the single largest, ugliest morass of code in C<Win32::ASP::DBRecord>. Yeach. Think of
- it as a slightly uglier C<insert>, though, and it's a little easier to understand.
-
- First we start a transaction and call C<can_update>. The C<can_update> method will call C<read>
- in turn (no way to know if we can update a record if we don't know what was in it).
-
- If C<can_update> returns false, we throw a C<Win32::ASP::Error::DBRecord::no_permission> exception
- and get out of here. Otherwise, we procede to call C<verify_record> and C<verify_timestamp>. If
- neither of those throw exceptions, we continue on.
-
- The method then creates <C$constraint>, a SQL C<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 C<@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 C<can_edit> returns true and that have changed, or that are
- mentioned in C<@ext_fields>, the passed parameter list. Fields updated as a result of being in
- C<@ext_fields> are not mentioned in the list of changed fields that is returned.
-
-
- =cut
-
- sub update {
- my $self = shift;
- my(@ext_fields) = @_;
-
- my(@retvals);
- $self->_DB->begin_trans;
- {
- my $identifier = join(", ", map {"$_ $self->{edit}->{$_}"} $self->_PRIMARY_KEY);
- $self->can_update or throw Win32::ASP::Error::DBRecord::no_permission(action => 'update', identifier => $identifier);
- $self->verify_record;
- $self->verify_timestamp;
-
- my $constraint = join(" AND ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($self->{orig}->{$_})} $self->_PRIMARY_KEY);
-
- my(@pairs);
- foreach my $field (values %{$self->_FIELDS}) {
- my $name = $field->name;
- if ($field->can_edit($self, 'orig') && $self->{edit}->{$name} ne $self->{orig}->{$name}) {
- push(@pairs, $field->as_write_pair($self, 'edit'));
- push(@retvals, {field => $name, newvalue => $self->{edit}->{$name}, oldvalue => $self->{orig}->{$name}});
- }
- }
-
- foreach my $field (@ext_fields) {
- push(@pairs, $self->_FIELDS->{$field}->as_write_pair($self, 'edit'));
- }
-
- $self->_DB->update($self->_WRITE_SRC, $constraint, @pairs);
-
- if (%{$self->_CHILDREN}) {
- my(%key_vals) = $self->clean_key_vals('orig');
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- if (ref($self->{$child})) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- foreach my $field (keys %key_vals) {
- $self->{$child}->set_prop($field, $key_vals{$field});
- }
- $self->{$child}->update;
- }
- }
- }
- }
- $self->_DB->commit_trans;
-
- return (@retvals);
- }
-
- sub delete {
- my $self = shift;
-
- $self->_DB->begin_trans;
- {
- my $identifier = join(", ", map {"$_ $self->{orig}->{$_}"} $self->_PRIMARY_KEY);
- $self->can_delete or throw Win32::ASP::Error::DBRecord::no_permission(action => 'delete', identifier => $identifier);;
- $self->verify_timestamp;
-
- my $constraint = join(" AND ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($self->{orig}->{$_})} $self->_PRIMARY_KEY);
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
- $self->_DB->exec_sql("DELETE FROM ".$type->_TYPE->_WRITE_SRC." WHERE $constraint");
- }
-
- $self->_DB->exec_sql("DELETE FROM ".$self->_WRITE_SRC." WHERE $constraint");
- }
- $self->_DB->commit_trans;
- }
-
- sub edit {
- my $self = shift;
-
- exists $self->{edit} and return;
- $self->{edit} = {%{$self->{orig}}};
-
- foreach my $child (keys %{$self->_CHILDREN}) {
- ref($self->{$child}) and $self->{$child}->edit;
- }
-
- }
-
- sub split {
- my $self = shift;
-
- my $class = ref($self);
-
- my $edit = $class->new;
- exists $self->{parent} and $edit->{parent} = $self->{parent};
- $edit->{edit} = $self->{edit};
- delete $self->{edit};
- return $edit;
- }
-
- sub merge {
- my $self = shift;
- my $edit = shift;
-
- $self->{edit} = $edit->{edit};
- }
-
- sub row_check {
- my $self = shift;
- my($row, @columns) = @_;
-
- scalar(@columns) or @columns = keys %{$self->_FIELDS};
-
- my $good = 0;
- foreach my $field (@columns) {
- if (defined $self->{edit}->{$field}) {
- $good = 1;
- last;
- }
- }
-
- return $good;
- }
-
- sub verify_record {
- my $self = shift;
-
- my $data = 'edit';
- foreach my $field (values %{$self->_FIELDS}) {
- if ($field->reqd($self, $data)) {
- defined $self->{$data}->{$field->name} or throw Win32::ASP::Error::Field::required (field => $field);
- }
- }
- }
-
- sub set_timestamp {
- my $self = shift;
- my($timestamp) = @_;
-
- $self->edit;
- $self->{edit}->{timestamp} = $timestamp;
- }
-
- sub verify_timestamp {
- my $self = shift;
-
- if (exists $self->_FIELDS->{timestamp}) {
- $self->{orig}->{timestamp} eq $self->{edit}->{timestamp} or
- throw Win32::ASP::Error::DBRecord::timestamp;
- }
- }
-
- sub can_view {
- my $self = shift;
-
- return 1;
- }
-
- sub can_delete {
- my $self = shift;
-
- return $self->can_update;
- }
-
- sub can_update {
- my $self = shift;
-
- $self->read;
- return 1;
- }
-
- sub can_insert {
- my $self = shift;
-
- return 1;
- }
-
- sub should_update {
- my $self = shift;
-
- $self->read;
- return 1;
- }
-
- sub clean_key_vals {
- my $self = shift;
- my($data, @key_vals) = @_;
-
- my %key_vals;
- @key_vals{$self->_PRIMARY_KEY} = @key_vals;
- foreach my $field (keys %key_vals) {
- $key_vals{$field} ne '' or $key_vals{$field} = $self->{$data}->{$field};
- }
- return %key_vals;
- }
-
-
- sub field {
- my $self = shift;
- my($fieldname, $data, $viewtype) = @_;
-
- exists $self->_FIELDS->{$fieldname} or
- throw Win32::ASP::Error::Field::non_existent (fieldname => $fieldname, method => 'Win32::ASP::DBRecord::field');
- return $self->_FIELDS->{$fieldname}->as_html($self, $data, $viewtype);
- }
-
- sub action_disp_trigger {
- my $self = shift;
- my($actionname) = @_;
-
- exists $self->_ACTIONS->{$actionname} or
- throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
- my $temp = $self->_ACTIONS->{$actionname}->disp_trigger($self);
- $temp and return $temp;
- return;
- }
-
- sub action_effect_from_asp {
- my $self = shift;
-
- my $actionname = $main::Request->querystring('action')->item;
- exists $self->_ACTIONS->{$actionname} or
- throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
- $self->_ACTIONS->{$actionname}->effect_from_asp($self);
- }
-
- sub action_disp_verify {
- my $self = shift;
-
- my $actionname = $main::Request->querystring('action')->item;
- exists $self->_ACTIONS->{$actionname} or
- throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
- return $self->_ACTIONS->{$actionname}->disp_verify($self);
- }
-
- sub action_disp_success {
- my $self = shift;
-
- my $actionname = $main::Request->querystring('action')->item;
- exists $self->_ACTIONS->{$actionname} or
- throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
- return $self->_ACTIONS->{$actionname}->disp_success($self);
- }
-
- sub debug_dump {
- my $self = shift;
-
- $main::Response->Write("<XMP>".Data::Dumper->Dump([$self], ['self'])."</XMP>");
- }
-
-
-
- #################### Error Classes ###################################
-
- package Win32::ASP::Error::DBRecord;
- @Win32::ASP::Error::DBRecord::ISA = qw/Win32::ASP::Error/;
-
-
- package Win32::ASP::Error::DBRecord::no_permission;
- @Win32::ASP::Error::DBRecord::no_permission::ISA = qw/Win32::ASP::Error::DBRecord/;
-
- #Parameters: action, identifier
-
- sub _as_html {
- my $self = shift;
-
- my $action = $self->action;
- my $identifier = $self->identifier;
- return <<ENDHTML;
- You are not allowed to $action $identifier.<P>
- ENDHTML
- }
-
-
- package Win32::ASP::Error::DBRecord::timestamp;
- @Win32::ASP::Error::DBRecord::timestamp::ISA = qw/Win32::ASP::Error::DBRecord/;
-
- sub _as_html {
- my $self = shift;
-
- return <<ENDHTML;
- The timestamp on this record has changed, indicating that someone else has made changes
- while you were attempting to make your changes.<P>
- <B>Your changes have been canceled.</B><P>
- To resubmit your changes:
- <UL>
- <LI>Press back until you are <B>viewing</B> the record.
- <LI>Click on the refresh link at the end of the page.
- <LI>Review the record.
- <LI>Resubmit the changes if you feel they are still warranted.
- </UL>
- ENDHTML
- }
-
- 1;
-
- =head1 BUGS
-
- =over 4
-
- =item 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.
-
- =back
-
- =cut
-
-