skip to main content
OpenEdge Development: ADM and SmartObjects
Developing Your Application's Business Logic : Validation procedures
 
Validation procedures
The SmartDataObject supports the definition of validation procedures for the following:
*RowObject temp-table fields
*The RowObject temp-table record as a whole
*Update transaction on the server side
This allows you to define several distinct levels of data validation, as discussed in this section.
Data Dictionary validation
As you might expect, Data Dictionary validation is simply the validation expression (and accompanying validation message) defined in the Data Dictionary. The temp-table columns inherit this validation for each column that maps to a single database field. Client objects can query it using the columnValExp and columnValMsg functions; for example, you might do this at design time to include Data Dictionary validation in the generated code for a non‑Progress visualization.
SmartDataBrowsers and SmartDataViewers include the same field definition list as the SmartDataObject. In this way, these objects automatically inherit the format, validation expression, and other field attributes of each field in the RowObject, as defined in the SmartDataObject. This field validation can be performed in the visualization even without a database connection available at run time (in the event that the application is distributed using AppServer), as long as the field validation does not require database lookups (CAN-FIND and so on). Because of this restriction, the column definition dialog box (which appears when you build a SmartDataObject) includes a toggle box that you can turn off to prevent the RowObject field from inheriting the field validation for the database field from which that column is derived. You should normally turn off this toggle box if the Data Dictionary validation requires a database lookup and the client might be run without a database connection of its own to satisfy that lookup.
The Data Dictionary validation is not automatically executed in the SmartDataObject itself. It normally occurs in the SmartDataViewer or SmartDataBrowser when saving changes to an entire row.
Field-level validation procedures
You can define field validation procedures (also called column validation procedures) in the SmartDataObject for each updateable field in the temp-table. To do this, you define an internal procedure named columnValidate that takes the column value as an input parameter. Each time the SmartDataObject receives an updated field from a client object, the submitRow function in the super procedure data.p runs the field validation procedure (if any). The procedure receives the field’s value as an INPUT parameter. If the validation fails, the procedure can signal an error and log a message by RETURNing the message text. This message is displayed to the user as the error text, and in the visualization, focus is applied to the invalid field.
The field‑level validation procedure allows more extensive value checking than the logical ValExp (schema validation expression) permits. Because the schema validation expression might not always be executed in the client visualization, it is a good idea to duplicate any important checks in the SmartDataObject field validation procedures. The field validation procedures are executed only if a field value changes.
Note: To work around the restriction that field‑level validation executes only when a field value changes, use record‑level validation instead of field‑level validation. This works because the record‑level validation procedure can access any field in the row object by name. For details, see the “Record-level validation procedures” section .
The following example, which utilizes the Progress sample database, describes a SmartObject application that implements a field‑level validation procedure in a SmartDataObject. Specifically, it creates field‑level validation that ensures that application users enter OrderLine quantities that are greater than zero.
To implement a field‑level validation procedure in a SmartDataObject in the AppBuilder:
1. Create a SmartWindow.
2. Create a SmartDataObject for the OrderLine table in the sports2000 sample database.
3. Create a SmartDataViewer for the OrderLine table, using the SmartDataObject created in Step 2 as its Data-Source.
4. Drop the SmartDataObject and SmartDataViewer onto the SmartWindow and link them with Data and Update links.
5. Drop a Navigation SmartPanel onto the SmartWindow and link it to the SmartDataObject with a Navigation link.
6. Drop an Update SmartPanel onto the SmartWindow and link it to the SmartDataViewer with a TableIO link.
7. Create in the SmartDataObject an internal procedure called QtyValidate that has the following code:
 
  DEFINE INPUT PARAMETER valqty AS CHARACTER NO-UNDO.
 
  IF INTEGER(valqty) = 0 THEN 
    RETURN "Order Line quantity must be greater than zero".
END PROCEDURE.
With this field‑level validation procedure in place, an application user who enters 0 (zero) for the Order Line quantity receives the specified error message, and focus is applied to that field in the SmartDataViewer. Note, however, that if the initial value of the Order Line quantity is 0 and the application user does not modify the field in the visualization, the validation does not run. This is because field validation procedures are executed only if a field value changes.
Record-level validation procedures
You can define a validation procedure called RowObjectValidate in the SmartDataObject for the temp-table record as a whole. This record‑level validation procedure (also called a row validation procedure) can perform cross‑field validation and other kinds of checks for a set of updates to a single record. The submitRow function in the super procedure data.p executes the record‑level validation procedure (if defined) whenever one or more field values in a record are updated, after the field validation procedures run. The procedure can access any field in the RowObject row by name and, like the field‑level validation procedure, it signals error by RETURNing an error message text to the caller.
Note: Because the RowObjectValidate procedure can access any field in the row object by name, you can use record‑level validation instead of field‑level validation to work around the restriction that field‑level validation executes only when a field value changes.
The fact that RowObjectValidate procedures in a SmartDataObject run whenever one or more field values in a record are updated has a side effect you must be aware of. If your RowObjectValidate procedure validates a field that is not included in a visualization that updates the table and the procedure returns an error, the application user cannot access the field or fix the error.
Caution: RowObjectValidate procedures run for every visualization that can add or update the table. If you add a RowObjectValidate procedure to a SmartDataObject, make sure any visualization that might fire the procedure includes the appropriate fields so application users can add or correct data as necessary to pass the validation check.
Suppose, for example, a customer SmartDataObject includes a RowObjectValidate procedure that ensures at least one of the following fields—phone, fax, email address—is populated for a customer record. If the application that uses this SmartDataObject includes a SmartDataViewer that can add customer records but does not include any of these fields, the validation procedure will not allow the application user to add a record and will return an error noting the user must enter either a phone, fax, or e‑mail address. However, the user cannot access these fields and will not be able to enter any of these values.
The following example, which uses the Progress sample database, describes a SmartObject application that implements a record‑level validation procedure in a SmartDataObject. Specifically, it creates record‑level validation that ensures that application users enter credit limits greater than zero.
To implement a record‑level validation procedure in a SmartDataObject:
1. Create a SmartWindow.
2. Create a SmartDataObject for the customer’s table that includes the name and creditlimit fields.
3. Create a SmartDataViewer for the SmartDataObject in Step 2, with its fields in it.
4. Drop the SmartDataObject and SmartDataViewer onto the SmartWindow and link them with Data and Update links.
5. Drop a Commit SmartPanel on the SmartWindow and link it to the SmartDataObject with a Commit link.
6. Drop a Save SmartPanel onto the SmartWindow and link it to the SmartDataViewer with a TableIO link.
7. Create in the SmartDataObject an internal procedure called RowObjectValidate that has the following code:
 
  If RowObject.CreditLimit <= 0 THEN 
    RETURN "Credit limit must be greater than zero".
END PROCEDURE.
With this record‑level validation procedure in place, an application user who enters zero for the credit limit receives the specified error message.
As another example, the following code, when placed in the RowObjectValidate internal procedure, cancels any data change (add or update) where the value of SalesRep is SLS (this is a new sales rep) and the Balance or CreditLimit fields are out of a specified range:
 
IF RowObject.SalesRep = "SLS": THEN
  IF RowObject.Balance > 0 OR RowObject.CreditLimit > 5000 THEN
    RETURN "SalesRep Not Authorized For This Change".
Transaction-level validation procedures
As updates are made, the ADM creates a version of the RowObject temp-table, called RowObjUpd, that contains only the rows that are being updated (that is, modified, added, or deleted). It passes the RowObjUpd temp-table to the server side of the Commit process, which finds the corresponding database records (using the RowIdent field) and moves the update rows into those records. ADM allows you to implement transaction‑level validation at various points in the process.
The SmartDataObject contains four entry points for transaction‑level validation procedures: preTransactionValidate, beginTransactionValidate, endTransactionValidate, and postTransactionValidate. These entry points are simply procedure names that execute NO-ERROR. If you want your SmartDataObject to perform custom validation at a particular point in a transaction, you simply write an internal procedure of the corresponding name in the SmartDataObject. These procedures are executed where the database connection is. If the SmartDataObject is divided between client and AppServer, they are executed on the AppServer.
The four entry points operate as follows:
*preTransactionValidate — This procedure is executed immediately before a transaction block begins. At this point, all rows in the RowObjUpd temp-table are available, can be read, and, if necessary, can be modified. This is the appropriate place to put checks that would result in canceling a transaction before it begins; for example, checks that verify the validity or consistency of rows being added, updated, or deleted. It also is the place to put code that changes or sets any values in those records that have not been set before; for example, totals.
*beginTransactionValidate — This procedure is executed immediately after the beginning of a transaction block. This is the appropriate place for business logic that:
*Updates related database records that should be updated as part of the same transaction
*Does not depend on SmartDataObject updates having been made to the database already
*endTransactionValidate — This procedure is executed immediately before the end of a transaction block. This is the appropriate place for:
*Business logic that updates related database records that should be updated as part of the same transaction
*Performing other checks that are dependent on SmartDataObject updates having been made to the database already
For example, if the logic for an Order SmartDataObject must total all orders to compare them with a credit limit, then it must be able to read not only updated order records but also any records already in the database. This logic is easier to implement if it is executed after the updated rows are written back to the database.
As another example, suppose you add to a SmartDataObject a row for which the following are true:
*It is a one‑to‑one join of two database tables.
*Its key field, which is the join field for the tables, is assigned from a numeric sequence in the database.
In this case, it is impossible to assign the key field value for the second table any sooner than at the end of the transaction. An endTransactionValidate procedure can read the primary table record from the database, retrieve the key value, and assign it to the secondary table record.
*postTransactionValidate — This procedure is executed immediately after the end of the transaction block. This is the appropriate place to put business logic that performs other checks that should not be part of the basic transaction; for example, sending status messages or other work that should be performed in a separate transaction or in no transaction at all.
Each of these procedures has access to the RowObjUpd table and to any connected database tables. The procedures signal error by RETURNing an error message; if any of these procedures does return an error message, processing stops. Thus:
*When preTransactionValidate returns an error, it cancels the update before the transaction begins.
*When beginTransactionValidate or endTransactionValidate returns an error, it undoes the transaction.
*When postTransactionValidate returns an error, there is no effect on the transaction: it does not undo the transaction because at that point, the transaction has already been completed.
Note: The version of ADM available in Progress V9.0 provided a single entry point called TransactionValidate for transaction‑level validation. In subsequent releases, the preTransactionValidate procedure effectively replaces TransactionValidate, but TransactionValidate is still supported for backward compatibility. As is the case with preTransactionValidate, TransactionValidate executes NO-ERROR so it runs only if it was defined for the SmartDataObject.
Database trigger procedures
Database trigger procedures are not part of the SmartDataObject model, and Progress Software Corporation recommends that you limit them to performing essential data integrity checks. There are two reasons for this recommendation:
*They are relatively more difficult to maintain and examine than code that is part of the business logic of an application.
*The checks are not performed until the actual database update is attempted.
By moving as much as possible of the application’s business logic as far from the database transaction as possible, you ensure that most data errors are reported earlier and more efficiently. If a database trigger fails during a SmartDataObject update, the transaction is aborted and the error message, if any, is reported back to the client object, where either the data must be modified or the update canceled.
The ADM provides a standard error reporting mechanism that database triggers can use. This mechanism, the addMessage procedure, allows the SmartDataObject to log one or more error messages and return them in a standard format to a visualization or other client object, which can then present them to the user as appropriate, including repositioning the cursor to the field where the error occurred. The addMessage procedure takes as arguments the message text, the field name, if applicable, and the table name, if applicable. If database trigger procedures run addMessage, these messages are returned to the client visualization for display, even across an AppServer connection. MESSAGE statements are not transmitted across an AppServer connection and are not, therefore, an appropriate way to log error messages in any database that might be used in distributed applications.