home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / DBRecord.pm < prev    next >
Encoding:
Perl POD Document  |  2001-11-05  |  26.5 KB  |  896 lines

  1. ############################################################################
  2. #
  3. # Win32::ASP::DBRecord - an abstract parent class for representing database
  4. #                        records in the Win32-ASP-DB system
  5. #
  6. # Author: Toby Everett
  7. # Revision: 0.02
  8. # Last Change:
  9. ############################################################################
  10. # Copyright 1999, 2000 Toby Everett.  All rights reserved.
  11. #
  12. # This file is distributed under the Artistic License. See
  13. # http://www.ActiveState.com/corporate/artistic_license.htm or
  14. # the license that comes with your perl distribution.
  15. #
  16. # For comments, questions, bugs or general interest, feel free to
  17. # contact Toby Everett at teverett@alascom.att.com
  18. ############################################################################
  19.  
  20. use Win32::ASP::Action;
  21. use Win32::ASP::Field;
  22. use Error qw/:try/;
  23. use Win32::ASP::Error;
  24.  
  25. package Win32::ASP::DBRecord;
  26.  
  27. use strict;
  28.  
  29. =head1 NAME
  30.  
  31. Win32::ASP::DBRecord - an abstract parent class for representing database records
  32.  
  33. =head1 SYNOPSIS
  34.  
  35. =head1 DESCRIPTION
  36.  
  37. The main purpose of C<Win32::ASP::DBRecord>is to be subclassed.  It implements a generic set of
  38. default behavior for the purpose of reading a record from a table, displaying that record in HTML,
  39. allowing edits to it, and writing that record back to the table.  It relies heavily upon
  40. Win32::ASP::Field objects, which are used to provide an object-oriented interface to the most
  41. important class data for a C<Win32::ASP::DBRecord> subclass - the fields possessed by the record
  42. represented by the class.
  43.  
  44. =head2 Internal Data Structure
  45.  
  46. The internal data structure of a instance of C<Win32::ASP::DBRecord> consists of the following
  47. elements, all optional:
  48.  
  49. =over 4
  50.  
  51. =item orig
  52.  
  53. This is a reference to a hash indexed on field names and storing data read from the database.
  54.  
  55. =item edit
  56.  
  57. This is a reference to a hash indexed on field names and storing the currently modified data.
  58.  
  59. =item I<child_dbgroup>
  60.  
  61. There can be any number of child groups and these are stored at the root of the
  62. C<Win32::ASP::DBRecord> object, not within C<orig> or C<edit>.  See C<_CHILDREN> for more
  63. information.
  64.  
  65. =back
  66.  
  67. =head2 Class Methods
  68.  
  69. Class methods were used to implement access to class properties.  Since Perl doesn't enforce a
  70. distinction between class and instance methods, these methods can be called on both class names
  71. and on instances of the class, which ends up being incredibly useful.  I strongly recommend against
  72. ever calling these methods using subroutine notation (i.e. C<&_DB> or C<&_PRIMARY_KEY>).
  73. Perl methods execute in the namespace in which they were defined, which means that if you further
  74. subclass and define a new implementation of those methods, any methods you don't override that were
  75. in the parent class will call the parent class's versions of those methods.  That's bad.  Always
  76. call these methods with the arrow notation and you'll be safe.
  77.  
  78. =head3 Mandatory Class Methods
  79.  
  80. These class methods will be overridden in every child class.
  81.  
  82. =over 4
  83.  
  84. =item _DB
  85.  
  86. The C<_DB> method should return the C<Win32::ASP::DB> (or subclass there-of) object that is used
  87. for database access.  A frequent implementation looks like this:
  88.  
  89.   sub _DB {
  90.     return $main::TheDB;
  91.   }
  92.  
  93. =cut
  94.  
  95. sub _DB {
  96.   return $main::TheDB;
  97. }
  98.  
  99. =item _FRIENDLY
  100.  
  101. The C<_FRIENDLY> method should return a friendly name expressing what sorts of things these
  102. records are.  This friendly name may get used in certain error messages (in particular,
  103. C<Win32::ASP::Error::Field::group_wrapper>).  For instance, the C<_FRIENDLY> method for line items
  104. on an invoice might return "Line Item".  An error message could then say, "There was an error in
  105. Line Item 4.  The error was . . ."
  106.  
  107. =cut
  108.  
  109. sub _FRIENDLY {
  110.   return 'MyRecord';
  111. }
  112.  
  113. =item _READ_SRC
  114.  
  115. The C<_READ_SRC> method should return the name of the table or view that should be used to read
  116. records from the database.  Frequently a view will be defined on the SQL Server to include
  117. information from various lookup tables.
  118.  
  119. =cut
  120.  
  121. sub _READ_SRC {
  122.   return 'MyView';
  123. }
  124.  
  125. =item _WRITE_SRC
  126.  
  127. The C<_WRITE_SRC> method should return the name of the table that should be used to write records
  128. to the database.
  129.  
  130. =cut
  131.  
  132. sub _WRITE_SRC {
  133.   return 'MyTable';
  134. }
  135.  
  136. =item _PRIMARY_KEY
  137.  
  138. The C<_PRIMARY_KEY> method should return a list of the field names in the Primary Key for the
  139. table.  Of note, this returns a B<list>, not a B<reference to an array>.  The order that the
  140. fields are in C<_PRIMARY_KEY> is also the order in which the values will be specified for
  141. identifying records for reading them from the database.
  142.  
  143. =cut
  144.  
  145. sub _PRIMARY_KEY {
  146.   return ('ID');
  147. }
  148.  
  149. =item _FIELDS
  150.  
  151. The C<_FIELDS> method should return a reference to a hash of C<Win32::ASP::Field> objects, indexed
  152. on the field names.  Of note, for performance reasons the method is usually implemented like so:
  153.  
  154.   sub _FIELDS {
  155.     return $MyStuff::MyRecord::fields;
  156.   }
  157.  
  158.   $MyStuff::MyRecord::fields = {
  159.  
  160.     Win32::ASP::Field->new(
  161.             name => 'RecordID',
  162.             sec  => 'ro',
  163.             type => 'int',
  164.             desc => 'Record ID',
  165.         ),
  166.  
  167.     Win32::ASP::Field->new(
  168.       name => 'RecordRemarks',
  169.       sec  => 'rw',
  170.       type => 'text',
  171.     ),
  172.  
  173.   };
  174.  
  175. =cut
  176.  
  177. sub _FIELDS {
  178.   return $Win32::ASP::DBRecord::fields;
  179. }
  180.  
  181. $Win32::ASP::DBRecord::fields = {
  182.  
  183. };
  184.  
  185.  
  186.  
  187. =back
  188.  
  189. =head3 Optional Class Methods
  190.  
  191. These class methods can be overriden in a child class.
  192.  
  193. =over 4
  194.  
  195. =item _ACTIONS
  196.  
  197. The C<_ACTIONS> method should return a reference to a hash of C<Win32::ASP::Action> objects,
  198. indexed on the action names.  Actions are used to implement things that users do to records.  For
  199. instance, a user might want to Edit a record.  Some users might not have permissions to edit some
  200. records though, and so it makes sense to implement an object that is responsible for determining
  201. whether a given user is able to execute a given action in a given circumstance.  The action is
  202. also responsible for displaying the appropriate HTML for a link that implements the action,
  203. knowing how to warn a user before the action is carried out, etc.  For more information, see the
  204. C<Win32::ASP::Action> class and its various sub-classes.
  205.  
  206. Of note, for performance reasons the method is usually implemented like so:
  207.  
  208.   sub _ACTIONS {
  209.     return $MyStuff::MyRecord::actions;
  210.   }
  211.  
  212.   $MyStuff::MyRecord::actions = {
  213.  
  214.     Win32::ASP::Action::Edit->new,
  215.  
  216.     Win32::ASP::Action::Delete->new,
  217.  
  218.     Win32::ASP::Action->new(
  219.       name   => 'cancel',
  220.       label  => 'Cancel',
  221.       . . .
  222.     ),
  223.  
  224.   };
  225.  
  226. =cut
  227.  
  228. sub _ACTIONS {
  229.   return $Win32::ASP::DBRecord::actions;
  230. }
  231.  
  232. $Win32::ASP::DBRecord::actions = {
  233.  
  234. };
  235.  
  236. =item _CHILDREN
  237.  
  238. Some records quite logically have child records.  For instance, a Purchase Order generally has a
  239. number of line-items on it, and this are usually implemented using a table that is 1:M linked to
  240. the Purchase Order table.  Within C<Win32::ASP::DBRecord> class objects, this is implemented
  241. through a reference to a C<Win32::ASP::DBRecordGroup> class object that contains the child records.
  242.  
  243. The implementation normally looks something like this:
  244.  
  245.   sub _CHILDREN {
  246.     return $MyStuff::MyRecord::children;
  247.   }
  248.  
  249.   $MyStuff::MyRecord::children = {
  250.  
  251.     child_records => {
  252.       type  => 'MyStuff::MyChildRecordGroup',
  253.       pkext => 'ChildID',
  254.     },
  255.  
  256.   };
  257.  
  258. The implication of the above is that C<MyStuff::MyRecord> objects have a group of associated
  259. C<MyStuff::MyChildRecord> objects, which are accessed through a C<MyStuff::MyChildRecordGroup>
  260. object.  The reference to that object will be stored in C<$self->{child_records}>.  The
  261. primary key of the C<MyStuff::MyChildRecord> objects will be the primary key for the
  262. C<MyStuff::MyRecord> objects plus the added field 'C<ChildID>'.  The index on the hash is referred
  263. to hereafter as the 'child group name'.
  264.  
  265. =cut
  266.  
  267. sub _CHILDREN {
  268.   return $Win32::ASP::DBRecord::children;
  269. }
  270.  
  271. $Win32::ASP::DBRecord::children = {
  272.  
  273. };
  274.  
  275.  
  276.  
  277. =back
  278.  
  279. =head3 Class Methods you probably won't override
  280.  
  281. There is only one of these.
  282.  
  283. =over 4
  284.  
  285. =item ADD_FIELDS
  286.  
  287. This method is called on the class in order to add new fields.  This is usually used by
  288. C<Win32::ASP::DBRecordGroup> objects to add C<Win32::ASP::Field::dispmeta> objects to the
  289. underlying C<Win32::ASP::DBRecord> object.  C<Win32::ASP::Field::dispmeta> objects are frequently
  290. used to display more than one field in a column when displaying a table of records (i.e. one field
  291. above the other).
  292.  
  293. =cut
  294.  
  295. sub ADD_FIELDS {
  296.   my $class = shift;
  297.   my(%fields) = @_;
  298.  
  299.   no strict;
  300.   foreach my $i (keys %fields) {
  301.     ${"${class}::fields"}->{$i} = $fields{$i};
  302.   }
  303. }
  304.  
  305.  
  306.  
  307. =back
  308.  
  309. =back
  310.  
  311. =head2 Instance Methods
  312.  
  313. =head3 new
  314.  
  315. This is a basic C<new> method.  Simply creates an anonymous hash and returns a reference.  The
  316. C<new> method is not responsible for reading data or anything else.  Just creating a new record
  317. object.  You will probably not need to override this method.
  318.  
  319. =cut
  320.  
  321. sub new {
  322.   my $class = shift;
  323.  
  324.   my $self = {
  325.   };
  326.   bless $self, $class;
  327.   return $self;
  328. }
  329.  
  330. =head3 init
  331.  
  332. This is used for initializing new records prior to being edited.  The code in ASP land for
  333. throwing up the edit screen when creating a new record looks something like this:
  334.  
  335.   use MyStuff::MyRecord;
  336.  
  337.   $record = MyStuff::MyRecord->new;
  338.   $record->init;
  339.   $record->edit;
  340.   $data = 'edit';
  341.   $viewtype = 'edit';
  342.  
  343. This is then followed by the <FORM> section.
  344.  
  345. Note that C<init> modifies C<orig>, not C<edit>.  Once C<orig> is modified, the C<edit> method is
  346. used to place the record in C<edit> mode.
  347.  
  348. =cut
  349.  
  350. sub init {
  351.   my $self = shift;
  352.  
  353.   $self->{orig} = {};
  354.  
  355.   foreach my $child (keys %{$self->_CHILDREN}) {
  356.     my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  357.     $self->{$child} = $type->new;
  358.   }
  359. }
  360.  
  361. =head3 read
  362.  
  363. The C<read> method is used, coincidentally, to read a record from the database.  It should be
  364. passed an array comprised of the primary key values for the record desired.
  365.  
  366. The C<read> method is responsible for reading all appropriate values for the record, and for
  367. reading any child records for which the child group name shows up in C<$self>.  The implications
  368. of this are important for providing appropriate behavior when C<update> is called.
  369.  
  370. The actual reading in of data from the ADO Recordset object is implemented by C<_read>.  This
  371. is done so that C<Win32::ASP::DBRecordGroup> object can execute a query and then make calls to
  372. C<_read> for each record returned.
  373.  
  374. =cut
  375.  
  376. sub read {
  377.   my $self = shift;
  378.   my(@key_vals) = @_;
  379.  
  380.   exists ($self->{orig}) and return;
  381.  
  382.   my(%key_vals) = $self->clean_key_vals('edit', @key_vals);
  383.   my $constraint = join(" AND\n  ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($key_vals{$_})} $self->_PRIMARY_KEY)."\n";
  384.   my $results = $self->_DB->exec_sql("SELECT * FROM ".$self->_READ_SRC."\nWHERE $constraint", error_no_records => 1);
  385.  
  386.   $self->_read($results);
  387.  
  388.  
  389.   if (%{$self->_CHILDREN}) {
  390.     foreach my $child (keys %{$self->_CHILDREN}) {
  391.       if (exists $self->{$child}) {
  392.         my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  393.         unless (ref($self->{$child})) {
  394.           $self->{$child} = $type->new;
  395.           $self->{$child}->{parent} = $self;
  396.         }
  397.         $self->{$child}->query({%key_vals}, $pkext);
  398.       }
  399.     }
  400.   }
  401. }
  402.  
  403. =head3 _read
  404.  
  405. The C<_read> method is responsible for reading the data from the ADO Recordset object (C<$result>)
  406. and entering it into the object.  It does this by looping over the fields in C<_FIELDS> and calling
  407. C<read> on each of them with the appropriate parameters.  Note that C<_read> accepts the optional
  408. parameter C<$columns> and passes this along in the call to C<read> on the C<Win32::ASP::Field>
  409. objects.  This is to minimize unneeded value retrieval calls when C<Win32::ASP::DBRecordGroup>
  410. objects are only interested in a few fields.  If C<$columns> is a reference to a hash, it will be
  411. interpreted as a list of the fieldnames of note.  However, to allow for more flexibility in
  412. implementation, the decision as to whether or not the field will actually be read is still left
  413. up to the C<Win32::ASP::Field> object.
  414.  
  415. In addition, C<_read> is responsible for calling C<can_view> on the resultant record object to see
  416. whether the user is allowed to view this record.  If C<can_view> returns false, C<_read> throws a
  417. C<Win32::ASP::Error::DBRecord::no_permission> exception
  418.  
  419. =cut
  420.  
  421. sub _read {
  422.   my $self = shift;
  423.   my($results, $columns) = @_;
  424.  
  425.   foreach my $field (values %{$self->_FIELDS}) {
  426.     $field->read($self, $results, $columns);
  427.   }
  428.  
  429.   unless ($self->can_view) {
  430.     my $identifier = join(", ", map {"$_ $self->{orig}->{$_}"} $self->_PRIMARY_KEY);
  431.     $self->{orig} = undef;
  432.     throw Win32::ASP::Error::DBRecord::no_permission (action => 'view', identifier => $identifier);
  433.   }
  434. }
  435.  
  436. =head3 read_deep
  437.  
  438. Since the C<read> method is responsible for reading in all child records for which there is an
  439. entry in C<$self>, the C<read_deep> method simply creates an entry in the C<$self> hash for each
  440. key in the hash returned from C<_CHILDREN>.
  441.  
  442. =cut
  443.  
  444. sub read_deep {
  445.   my $self = shift;
  446.   my(@key_vals) = @_;
  447.  
  448.   foreach my $child (keys %{$self->_CHILDREN}) {
  449.     $self->{$child} = undef;
  450.   }
  451.   $self->read(@key_vals);
  452. }
  453.  
  454. =head3 post
  455.  
  456. The C<post> method takes data returned from a POST action and enters it into the
  457. C<Win32::ASP::DBRecord> object.  Of note, C<post> takes a C<$row> as a parameter.  This is used to
  458. identify which row of a table is of interest when being used for editing
  459. C<Win32::ASP::DBRecordGroup> objects.
  460.  
  461. The method simply calls C<post> on each of the C<Win32::ASP::Field> objects.
  462.  
  463. It also posts the data for all of the child records.  The presumption is that if the records are
  464. really child records, one would generally edit the whole mess at one time and that they will then
  465. want to be posted.  So it creates new child objects of the appropriate
  466. C<Win32::ASP::DBRecordGroup> classes and calls C<post> on them.
  467.  
  468. =cut
  469.  
  470. sub post {
  471.   my $self = shift;
  472.   my($row) = @_;
  473.  
  474.   foreach my $field (values %{$self->_FIELDS}) {
  475.     $field->post($self, $row);
  476.   }
  477.  
  478.   foreach my $child (keys %{$self->_CHILDREN}) {
  479.     my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  480.     $self->{$child} = $type->new;
  481.     $self->{$child}->{parent} = $self;
  482.     $self->{$child}->post;
  483.   }
  484. }
  485.  
  486. =head3 insert
  487.  
  488. The C<insert> method is responsible for taking the data and writing it to the database.  If there
  489. are child records associated with object, those are written as well.  Everything is wrapped in a
  490. transaction so that a failure to write child records for any reason will roll back the
  491. transaction.
  492.  
  493. The C<insert> method is passed a list of fields that should always be written.  By default, the
  494. C<insert> method will only write values that are considered editable (as determined by calling
  495. C<can_edit> on the field object) <Bor> that show up in the passed list of fields.  This enables
  496. one to define certain fields as read only, but still modify them within the context of actions or
  497. other code.  Also, values are only written if they are defined in the C<$self->{edit}> hash.
  498. It is generally considered poor form to write NULL values to the database (especially in SQL
  499. Server 6.5 as this results in a 2K page being allocated for NULL text objects:).
  500.  
  501. The values are prepared for inserting by calling C<as_write_pair> on the C<Win32::ASP::Field>
  502. objects.  The array of write pairs is then passed to the C<insert> method on the C<Win32::DB>
  503. object.  The return from that call is the ADO Recordset object, which is then passed to
  504. C<set_inserted> so that auto generated Primary Key values can be retrieved
  505.  
  506. It then deals with the child record groups as needed.  The defined objects have C<set_prop> used
  507. to propagate the primary key values onto the child objects.  The C<insert> method can then be
  508. called to insert the entire group.
  509.  
  510. The C<insert> method returns a list of all write pairs that were inserted.  This so that
  511. implementations that override C<insert> can make use of that information (this is most commonly
  512. done for logging purposes - other records are inserted into logging tables to indicate who did
  513. what when, and having C<insert> return the information makes that much easier.).
  514.  
  515. =cut
  516.  
  517. sub insert {
  518.   my $self = shift;
  519.   my(@ext_fields) = @_;
  520.  
  521.   my(@pairs);
  522.   $self->_DB->begin_trans;
  523.   {
  524.     $self->verify_record;
  525.  
  526.     my(%ext_fields) = map {($_, 1)} @ext_fields;
  527.     foreach my $field (values %{$self->_FIELDS}) {
  528.       if (($ext_fields{$field->name} or $field->can_edit($self, 'edit')) && defined $self->{edit}->{$field->name}) {
  529.         push(@pairs, $field->as_write_pair($self, 'edit'));
  530.       }
  531.     }
  532.  
  533.     $self->set_inserted($self->_DB->insert($self->_WRITE_SRC, @pairs));
  534.  
  535.     if (%{$self->_CHILDREN}) {
  536.       my(%key_vals) = $self->clean_key_vals('edit');
  537.  
  538.       foreach my $child (keys %{$self->_CHILDREN}) {
  539.         if (ref($self->{$child})) {
  540.           my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  541.           foreach my $field (keys %key_vals) {
  542.             $self->{$child}->set_prop($field, $key_vals{$field});
  543.           }
  544.           $self->{$child}->insert;
  545.         }
  546.       }
  547.     }
  548.   }
  549.   $self->_DB->commit_trans;
  550.  
  551.   return (@pairs);
  552. }
  553.  
  554. =head3 set_inserted
  555.  
  556. This method is responsible for retrieving the Primary Key values on newly inserted records.  Most
  557. useful when one of those Primary Key values is an autonumber field.
  558.  
  559. =cut
  560.  
  561. sub set_inserted {
  562.   my $self = shift;
  563.   my($results) = @_;
  564.  
  565.   foreach my $i ($self->_PRIMARY_KEY) {
  566.     $self->{edit}->{$i} = $results->Fields->Item($i)->Value;
  567.   }
  568. }
  569.  
  570. =head3 update
  571.  
  572. This is the single largest, ugliest morass of code in C<Win32::ASP::DBRecord>.  Yeach.  Think of
  573. it as a slightly uglier C<insert>, though, and it's a little easier to understand.
  574.  
  575. First we start a transaction and call C<can_update>.  The C<can_update> method will call C<read>
  576. in turn (no way to know if we can update a record if we don't know what was in it).
  577.  
  578. If C<can_update> returns false, we throw a C<Win32::ASP::Error::DBRecord::no_permission> exception
  579. and get out of here.  Otherwise, we procede to call C<verify_record> and C<verify_timestamp>.  If
  580. neither of those throw exceptions, we continue on.
  581.  
  582. The method then creates <C$constraint>, a SQL C<WHERE> condition suitable for indentifying the
  583. record of interest based on the Primary Key.
  584.  
  585. It then starts building a list of write pairs.  It also adds those pairs to C<@retvals>, which
  586. will contain a list of fields, new values, and old values for any field that changed.  Note that
  587. we only update fields for which C<can_edit> returns true and that have changed, or that are
  588. mentioned in C<@ext_fields>, the passed parameter list.  Fields updated as a result of being in
  589. C<@ext_fields> are not mentioned in the list of changed fields that is returned.
  590.  
  591.  
  592. =cut
  593.  
  594. sub update {
  595.   my $self = shift;
  596.   my(@ext_fields) = @_;
  597.  
  598.   my(@retvals);
  599.   $self->_DB->begin_trans;
  600.   {
  601.     my $identifier = join(", ", map {"$_ $self->{edit}->{$_}"} $self->_PRIMARY_KEY);
  602.     $self->can_update or throw Win32::ASP::Error::DBRecord::no_permission(action => 'update', identifier => $identifier);
  603.     $self->verify_record;
  604.     $self->verify_timestamp;
  605.  
  606.     my $constraint = join(" AND ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($self->{orig}->{$_})} $self->_PRIMARY_KEY);
  607.  
  608.     my(@pairs);
  609.     foreach my $field (values %{$self->_FIELDS}) {
  610.       my $name = $field->name;
  611.       if ($field->can_edit($self, 'orig') && $self->{edit}->{$name} ne $self->{orig}->{$name}) {
  612.         push(@pairs, $field->as_write_pair($self, 'edit'));
  613.         push(@retvals, {field => $name, newvalue => $self->{edit}->{$name}, oldvalue => $self->{orig}->{$name}});
  614.       }
  615.     }
  616.  
  617.     foreach my $field (@ext_fields) {
  618.       push(@pairs, $self->_FIELDS->{$field}->as_write_pair($self, 'edit'));
  619.     }
  620.  
  621.     $self->_DB->update($self->_WRITE_SRC, $constraint, @pairs);
  622.  
  623.     if (%{$self->_CHILDREN}) {
  624.       my(%key_vals) = $self->clean_key_vals('orig');
  625.  
  626.       foreach my $child (keys %{$self->_CHILDREN}) {
  627.         if (ref($self->{$child})) {
  628.           my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  629.           foreach my $field (keys %key_vals) {
  630.             $self->{$child}->set_prop($field, $key_vals{$field});
  631.           }
  632.           $self->{$child}->update;
  633.         }
  634.       }
  635.     }
  636.   }
  637.   $self->_DB->commit_trans;
  638.  
  639.   return (@retvals);
  640. }
  641.  
  642. sub delete {
  643.   my $self = shift;
  644.  
  645.   $self->_DB->begin_trans;
  646.   {
  647.     my $identifier = join(", ", map {"$_ $self->{orig}->{$_}"} $self->_PRIMARY_KEY);
  648.     $self->can_delete or throw Win32::ASP::Error::DBRecord::no_permission(action => 'delete', identifier => $identifier);;
  649.     $self->verify_timestamp;
  650.  
  651.     my $constraint = join(" AND ", map {"$_ = ".$self->_FIELDS->{$_}->as_sql($self->{orig}->{$_})} $self->_PRIMARY_KEY);
  652.  
  653.     foreach my $child (keys %{$self->_CHILDREN}) {
  654.       my($type, $pkext) = @{$self->_CHILDREN->{$child}}{'type', 'pkext'};
  655.       $self->_DB->exec_sql("DELETE FROM ".$type->_TYPE->_WRITE_SRC." WHERE $constraint");
  656.     }
  657.  
  658.     $self->_DB->exec_sql("DELETE FROM ".$self->_WRITE_SRC." WHERE $constraint");
  659.   }
  660.   $self->_DB->commit_trans;
  661. }
  662.  
  663. sub edit {
  664.   my $self = shift;
  665.  
  666.   exists $self->{edit} and return;
  667.   $self->{edit} = {%{$self->{orig}}};
  668.  
  669.   foreach my $child (keys %{$self->_CHILDREN}) {
  670.     ref($self->{$child}) and $self->{$child}->edit;
  671.   }
  672.  
  673. }
  674.  
  675. sub split {
  676.   my $self = shift;
  677.  
  678.   my $class = ref($self);
  679.  
  680.   my $edit = $class->new;
  681.   exists $self->{parent} and $edit->{parent} = $self->{parent};
  682.   $edit->{edit} = $self->{edit};
  683.   delete $self->{edit};
  684.   return $edit;
  685. }
  686.  
  687. sub merge {
  688.   my $self = shift;
  689.   my $edit = shift;
  690.  
  691.   $self->{edit} = $edit->{edit};
  692. }
  693.  
  694. sub row_check {
  695.   my $self = shift;
  696.   my($row, @columns) = @_;
  697.  
  698.   scalar(@columns) or @columns = keys %{$self->_FIELDS};
  699.  
  700.   my $good = 0;
  701.   foreach my $field (@columns) {
  702.     if (defined $self->{edit}->{$field}) {
  703.       $good = 1;
  704.       last;
  705.     }
  706.   }
  707.  
  708.   return $good;
  709. }
  710.  
  711. sub verify_record {
  712.   my $self = shift;
  713.  
  714.   my $data = 'edit';
  715.   foreach my $field (values %{$self->_FIELDS}) {
  716.     if ($field->reqd($self, $data)) {
  717.       defined $self->{$data}->{$field->name} or throw Win32::ASP::Error::Field::required (field => $field);
  718.     }
  719.   }
  720. }
  721.  
  722. sub set_timestamp {
  723.   my $self = shift;
  724.   my($timestamp) = @_;
  725.  
  726.   $self->edit;
  727.   $self->{edit}->{timestamp} = $timestamp;
  728. }
  729.  
  730. sub verify_timestamp {
  731.   my $self = shift;
  732.  
  733.   if (exists $self->_FIELDS->{timestamp}) {
  734.     $self->{orig}->{timestamp} eq $self->{edit}->{timestamp} or
  735.         throw Win32::ASP::Error::DBRecord::timestamp;
  736.   }
  737. }
  738.  
  739. sub can_view {
  740.   my $self = shift;
  741.  
  742.   return 1;
  743. }
  744.  
  745. sub can_delete {
  746.   my $self = shift;
  747.  
  748.   return $self->can_update;
  749. }
  750.  
  751. sub can_update {
  752.   my $self = shift;
  753.  
  754.   $self->read;
  755.   return 1;
  756. }
  757.  
  758. sub can_insert {
  759.   my $self = shift;
  760.  
  761.   return 1;
  762. }
  763.  
  764. sub should_update {
  765.   my $self = shift;
  766.  
  767.   $self->read;
  768.   return 1;
  769. }
  770.  
  771. sub clean_key_vals {
  772.   my $self = shift;
  773.   my($data, @key_vals) = @_;
  774.  
  775.   my %key_vals;
  776.   @key_vals{$self->_PRIMARY_KEY} = @key_vals;
  777.   foreach my $field (keys %key_vals) {
  778.     $key_vals{$field} ne '' or $key_vals{$field} = $self->{$data}->{$field};
  779.   }
  780.   return %key_vals;
  781. }
  782.  
  783.  
  784. sub field {
  785.   my $self = shift;
  786.   my($fieldname, $data, $viewtype) = @_;
  787.  
  788.   exists $self->_FIELDS->{$fieldname} or
  789.       throw Win32::ASP::Error::Field::non_existent (fieldname => $fieldname, method => 'Win32::ASP::DBRecord::field');
  790.   return $self->_FIELDS->{$fieldname}->as_html($self, $data, $viewtype);
  791. }
  792.  
  793. sub action_disp_trigger {
  794.   my $self = shift;
  795.   my($actionname) = @_;
  796.  
  797.   exists $self->_ACTIONS->{$actionname} or
  798.       throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
  799.   my $temp = $self->_ACTIONS->{$actionname}->disp_trigger($self);
  800.   $temp and return $temp;
  801.   return;
  802. }
  803.  
  804. sub action_effect_from_asp {
  805.   my $self = shift;
  806.  
  807.   my $actionname = $main::Request->querystring('action')->item;
  808.   exists $self->_ACTIONS->{$actionname} or
  809.       throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
  810.   $self->_ACTIONS->{$actionname}->effect_from_asp($self);
  811. }
  812.  
  813. sub action_disp_verify {
  814.   my $self = shift;
  815.  
  816.   my $actionname = $main::Request->querystring('action')->item;
  817.   exists $self->_ACTIONS->{$actionname} or
  818.       throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
  819.   return $self->_ACTIONS->{$actionname}->disp_verify($self);
  820. }
  821.  
  822. sub action_disp_success {
  823.   my $self = shift;
  824.  
  825.   my $actionname = $main::Request->querystring('action')->item;
  826.   exists $self->_ACTIONS->{$actionname} or
  827.       throw Win32::ASP::Error::Action::non_existent (actionname => $actionname, method => 'Win32::ASP::DBRecord::action_disp_trigger');
  828.   return $self->_ACTIONS->{$actionname}->disp_success($self);
  829. }
  830.  
  831. sub debug_dump {
  832.   my $self = shift;
  833.  
  834.   $main::Response->Write("<XMP>".Data::Dumper->Dump([$self], ['self'])."</XMP>");
  835. }
  836.  
  837.  
  838.  
  839. #################### Error Classes ###################################
  840.  
  841. package Win32::ASP::Error::DBRecord;
  842. @Win32::ASP::Error::DBRecord::ISA = qw/Win32::ASP::Error/;
  843.  
  844.  
  845. package Win32::ASP::Error::DBRecord::no_permission;
  846. @Win32::ASP::Error::DBRecord::no_permission::ISA = qw/Win32::ASP::Error::DBRecord/;
  847.  
  848. #Parameters:  action, identifier
  849.  
  850. sub _as_html {
  851.   my $self = shift;
  852.  
  853.   my $action = $self->action;
  854.   my $identifier = $self->identifier;
  855.   return <<ENDHTML;
  856. You are not allowed to $action $identifier.<P>
  857. ENDHTML
  858. }
  859.  
  860.  
  861. package Win32::ASP::Error::DBRecord::timestamp;
  862. @Win32::ASP::Error::DBRecord::timestamp::ISA = qw/Win32::ASP::Error::DBRecord/;
  863.  
  864. sub _as_html {
  865.   my $self = shift;
  866.  
  867.   return <<ENDHTML;
  868. The timestamp on this record has changed, indicating that someone else has made changes
  869. while you were attempting to make your changes.<P>
  870. <B>Your changes have been canceled.</B><P>
  871. To resubmit your changes:
  872. <UL>
  873. <LI>Press back until you are <B>viewing</B> the record.
  874. <LI>Click on the refresh link at the end of the page.
  875. <LI>Review the record.
  876. <LI>Resubmit the changes if you feel they are still warranted.
  877. </UL>
  878. ENDHTML
  879. }
  880.  
  881. 1;
  882.  
  883. =head1 BUGS
  884.  
  885. =over 4
  886.  
  887. =item Triple level children
  888.  
  889. The implementation of child records does not deal properly with situation in which the child
  890. records have children themselves.  This issue will be resolved when I have time.
  891.  
  892. =back
  893.  
  894. =cut
  895.  
  896.