Release 10.1A: OpenEdge Development:
Progress Dynamics Basic Development

> ## New Progress Dynamics validation procedures

> Progress Dynamics also supports additional standard internal procedure hooks that let you write validation logic for specific operations (`Create`, `Write`, and `Delete`), one record at a time. This spares you from writing a `FOR` `EACH` loop in each validation procedure to allow for the possibility that more than one row is being updated in a single transaction. It also removes the need to check the value of `RowMod` or in any other way write code specific to the structure of the `RowObjUpd` table. Among other things, this structure makes it possible to use the same logic procedure to validate a buffer outside the context of an SDO.

> For each stage in the transaction as described above (`preTransaction`, `beginTransaction`, `endTransaction`, and `postTransaction`), three procedure names are defined which, if they exist, will be called to deal with specific operations a row at a time. This, then, gives you a total of twelve additional hooks that you can use as needed:

> - `createPreTransValidate`

> - `createBeginTransValidate`

> - `createEndTransValidate`

> - `createPostTransValidate`

> - `writePreTransValidate`

> - `writeBeginTransValidate`

> - `writeEndTransValidate`

> - `writePostTransValidate`

> - `deletePreTransValidate`

> - `deleteBeginTransValidate`

> - `deleteEndTransValidate`

> - `deletePostTransValidate`

> Note: The names are shortened by changing Transaction to Trans.

> These hooks are called exactly when you would expect, based on their names. The create group hooks are called instead of their pre/begin/end/post counterparts for each newly created row, that is, a row that results from either an add or copy operation.

> The hooks in the write group are called for each update, add, or copy operation, the delete group for each delete operation.

> To make the logic as general as possible, and to support calling this validation logic from outside of SDOs, the buffer name of the table being updated is not `RowObjUpd` but rather the table name preceded by `b_`. This buffer name is available to the internal procedure. You do not need to define it there or pass it in as a parameter. (These internal procedures take no parameters at all.) In the case of the write group of procedures, the unchanged version of the record as it was originally read from the database is also available in a buffer with the table name prefixed by `old_`.

> In addition, because the write procedures will be called for adds and copies as well as updates (since you will want to place business logic there to validate values being written to the database for all of these cases), there is an additional logical variable named `isCreate`. The `isCreate` variable is available within the validation procedures. It will be `TRUE` if the operation is an add or copy, and `FALSE` otherwise.

> Note that the client-side validation procedure `rowObjectValidate` has not been subdivided into multiple other procedure hooks. This is partly because it is called for each individual Save operation, and therefore never needs to refer to multiple records as the server-side procedures must be prepared to do. Also, the principal goal of the logic procedure is to make the server-side logic, where most code is expected to go, as flexible and reusable as possible. However, the `b_ +` `<tablename>` buffer is defined for `rowObjectValidate` and available to it, so that the developer does not have to write references to the `RowObject` buffer.

> The framework gives you the option of using one or the other of these two sets of procedures in a single SDO, but not both. That is, an SDO (and its supporting logic procedure if it has one) can use the `pre/begin/end/postTransactionValidate` procedures as in the standard ADM. Alternatively, it can use the new create/write/delete set of procedure names. It cannot use both (this is largely to avoid confusing issues of the exact order in which all the procedures would be executed).

> In general, the recommendation is that new application objects should use the new procedures. They are more fine-grained. That is, they give you logic for a more specific update type and free you from the `RowObjUpd` and `RowMod` references of the other procedures. Also, they are more reusable from elsewhere in your application. The `TransactionValidate` procedures provide compatibility with existing Progress Version 9 applications.

> Chapter 5, "Using the Object Generator," shows some examples of the code for these new procedures, as automatically generated for you by the Object Generator. Here is an example:

>     PROCEDURE deletePreTransValidate:
>     /*------------------------------------------------------------------------
>      Purpose:   Procedure used to validate records server-side before the
>             transaction scope on delete of Order
>      Parameters: <none>
>      Notes:    Restrict delete of Order with OrderLine records.
>     -------------------------------------------------------------------------*/
>      IF CAN-FIND(FIRST OrderLine
>            WHERE OrderLine.OrderNum = b_Order.OrderNum) THEN
>      DO:
>        ERROR-STATUS:ERROR = NO.
>        RETURN {aferrortxt.i 'OE' '8' 'Order' '' STRING(b_Order.OrderNum)}.
>      END.
>     END PROCEDURE.

> This code checks to see if there is an existing `OrderLine` for the `Order` being deleted. If there is, the delete is rejected. The use of the standard error-formatting include file `aferrortxt.i` is explained in more detail in the "Message handling in Progress Dynamics" section. In this case, error message number 8 from the Order Entry (OE) group is returned with a substitution argument indicating the Order Number. So the message as stored in the message table in the Repository is something like: `Can’t delete Order Number` `<&1>``, which has Order Lines.`”

> The standard Progress 4GL `ERROR-STATUS` is reset as a matter of course, because the error- handling code will rely on the message being returned as the error flag.

> Note the use of the database table name `OrderLine` to refer to the database lookup, and the SDO buffer name `b_Order` to refer to the record being deleted.

> The following example shows the use of the Old buffer holding the original record as read from the database:

>     PROCEDURE writePreTransValidate:
>     /*------------------------------------------------------------------------
>      Purpose:   Procedure used to validate records server-side before the
>             transaction scope on write
>      Parameters: <none>
>      Notes:
>     -------------------------------------------------------------------------*/
>      DEFINE VARIABLE cMessageList  AS CHARACTER  NO-UNDO.
>      DEFINE VARIABLE cValueList   AS CHARACTER  NO-UNDO.
>      IF b_Customer.CreditLimit – Old_Customer.CreditLimit > 10000 THEN
>      DO:
>        ASSIGN
>         cValueList  = STRING(b_Customer.CreditLimit)
>         cMessageList = cMessageList +
>      (IF NUM-ENTRIES(cMessageList,CHR(3)) > 0 THEN CHR(3) ELSE '':U) +
>          {aferrortxt.i 'CM' '8' 'Customer' 'CreditLimit' cValueList }.
>      END.

>      ERROR-STATUS:ERROR = NO.
>      RETURN cMessageList.
>     END PROCEDURE.

> In this example, the `CreditLimit` of the original version of the record as read from the database is compared with the modified value. If this has been increased by more than $10,000, the change is rejected, using message 8 in the Customer Maintenance (CM) message group.

> Note the accumulation of messages here. More than one message can be returned to the client, delimited by `CHR(3)`. In this case, the ellipsis represents additional error checks that might be done. If the intent is to check for all possible errors and return a list of them, rather than returning the first error encountered, then this coding style accomplishes that. Each error text is added to a variable, and the entire list is returned to the client where it will be properly parsed and presented to the user.

> Note: If you want to implement initialization functionality for data-handling objects (SDOs, SBOs), you can override the `createobjects()` method of the container. Override with a check for `Valid-Handle` and access-contained instances. This technique allows you to add functionality before the main initialization.

> It is advisable to check all forms of validity in application code, even if there exists either a database trigger procedure or, as in this case, a built-in database check to perform the validation for you. If your code catches an error such as this, you can return a meaningful (and translatable) message to the user rather than waiting for Progress to attempt to display what might be a much less meaningful message, and one that might not make it back to the client successfully. And your code can react to the error exactly as you want, rather than trapping default Progress behavior.

Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095