Tech News Back Issues Issue: 072106

Data Objects

By David Swain
Polymath Business Systems

An Object Class intended for use as a Data Object is one that contains instance variables that can be used for data entry and/or display of values on windows and reports of an application. It also contains methods for manipulating the format, storage and retrieval of those values. The use of such Objects allows us to define custom data types, to simultaneously work with multiple records from the same table in different windows (or even within the same window) and even to work with well-defined combinations of tables (an entire invoicing structure, for example) within a single construct.

These are powerful capabilities. To truly cover all of these topics with the detail they each deserve would require a series of rather lengthy articles that would easily take us through the end of the year. But there will soon be other topics with which we must deal (version 4.2 of Omnis Studio has been announced and, therefore, will soon be shipping) and we will need to divert our attention to more current issues for a few months.

So this article will map out the concept of each level of Data Object complexity without going into line-by-line detail of specific implementations. But before we take on each of these Object implementation techniques separately, let us first consider what they all have in common. There is one new bit of Omnis Studio technology (at least, one that we haven't yet discussed in this article series) that ties these three Data Object techniques together...

Using Object Instance Variables on Class Layouts

Yes, you heard (or rather, read) me correctly. We can use instance variables that belong to in-scope Object and Object reference variables as the dataname values for fields on windows and reports. That is, we can perform data entry tasks directly on such variables. This is a key ingredient in the Data Object recipe. If we did not have this ability, Data Objects would be much less useful.

How do we do this? It is really quite simple. We just use the notational path to the variable inside the Object Instance as the dataname value of our field, just like we do when specifying a cell from a list variable in the same situation. For example, suppose we have an Object Class named dataDemoObject (the class name does not really matter) and it contains an instance variable named demoVar. We then instantiate this Object using a variable (of either Object or Object reference data type - this doesn't matter either) named objectVar. In this case, the objectVar variable is itself defined as an instance variable within a Window Class. We can then place an Entry field (or any other appropriate field type) on that Window Class and give it a dataname value of objectVar.demoVar. This allows us to display the value of the instance variable inside the Object Instance as well as to capture a new value entered by the user.

Instance Variable in Object Instance as Dataname Value

The variable demoVar is nested inside objectVar, which in turn belongs to our window. If the instance variable inside our Object were a row variable and we wanted to target the lastname column, we would simply drill down a little further:

Column of Embedded Row Variable Used as Dataname Value

When our window is instantiated, we can drill down through a series of Variable Value windows to observe the contents of our nested variables using the features of the Omnis Studio Debugger:

Variable Value Windows for Nested Variable Levels

If we need to use this variable in a method command somewhere within the window (as the target of a Calculate command, for example), we address it in exactly the same way.

Nested Variable as Target of Calculate Command

The point here is that Omnis Studio gives us very flexible and extensible access to variables nested inside Object Instances. We can do the same thing using a class variable from our Object Class, but I am focusing on instance variables because each instance of our Object can be used to represent a different value - or collection of values.

Some objects we decide to use in this way may have only one instance variable, while others may contain many. But even if the Object contains only one variable, that variable could be of Row or List - or even Object - data type and, therefore, could contain many variables itself. To use any discreet, single-valued variable nested within this structure as an element of an expression or as the dataname for a field, we just use the notational path to it within the current context. As we work further into this article, we will see more complex uses of this technique.

Custom Data Types

Let's begin with what is perhaps the simplest use of a Data Object. While Omnis Studio provides us with a useful assortment of native data types, there are certain specialized kinds of values that we may have to occasionally manufacture for ourselves from the building blocks built into the program. Here are a few possibilities:

  • Timestamp with timezone
  • SMTPE Timecode
  • Latitude and longitude
  • Chemical formula
  • Odds ratio (or other types of ratio)

Each of these types of value have special needs, including operations for combining with other values of the same type, component value range limits, presentation format and ordering mechanism. Sure, we could use a Function Object to contain all the methods required to work with a given type of data, but housing the value within the Object as well gives us a more complete "package" - one that we can even store directly in our database if we wish.

A quick example should help clarify this concept: Suppose we are creating a database that manages multimedia clips. One important feature of a clip is its duration, which we would measure using industry-standard SMTPE Timecode values. Examining such a value, we find that it has component parts of hours, minutes, seconds and frames. We also soon learn that we will need to be able to perform some basic arithmetic with these values, such as adding a few of them to determine a total duration or finding the difference between two durations. Our first decisions in creating a custom data type for such a value are interrelated: How do we want to store this value and how can we carry out the necessary range of operations in combining values?

We have a few options here: We can either store each component of the timecode value separately in its own column or we can combine all the components into a "background" value that we pack for storage and then unpack for display purposes. If we choose to store the components separately, then the obvious choice for the data type of each component is Short integer, since none of them (except for possibly the hours component) ever exceeds a value of 59 and none of them needs to ever be negative. If we choose to store one composite value, we need to decide among using a Character (store a string that includes the formatting punctuation), Number (reduce the value to the total number of frames) or Binary (use a separate byte - or even a well-defined range of bits - for each component, like Omnis Studio does with Date-Time values) column. Good arguments can be made for each. In practice, this value would then have to be parsed into its components for display purposes (unless we use Character data type), but we could manage to perform any required operations directly on the combined value if we choose either Number or Binary data type.

A little more thought reveals that there is one important element we have not yet considered: the frame rate. We can't perform any arithmetic operations using these values unless we know how many frames constitutes a second - and this can vary from one clip to another. So we have to again decide whether to store this information in a separate column or to combine it with the rest into a single composite value. Only a Binary column could handle such a composite this time.

Whatever our decisions are on all these issues, we can embody them within an Object Class - including instance variables for whatever means we decide upon for data entry and a mechanism to transmogrify such values between their storage containers in the CRB or select table and their display/manipulation containers in the Object Instance. The relative advantages and disadvantages of each possibility will be influenced by other needs of the application and, ultimately, by the preferences of the programmer.

Custom data types are relatively rare in practice, but they fall into the general category of Data Objects when implemented as described here.

Database Gateway Objects

The most common use of a Data Object is as a gateway between the database and the GUI interface elements we use to display and capture our data. In this case, the Object is used to represent a table from the database. It will most likely contain a row variable (containing all columns from the table) for performing data entry and storage operations on individual records and a list variable (or, perhaps, more than one on some occasions, but containing only a subset of the columns from the table) for searching and selection purposes. These structured variables can be defined from either a File, a Schema or a Table Class. The same technique can be used, therefore, on either native Omnis database elements or SQL database elements.

Even if we use a Table Class for housing most of the data access and update code for a database table, a Data object can be used to supplement the Table Class. For one thing, it can contain methods for synchronizing the row and list variables mentioned above so that the row variable always contains the complete contents of the record pointed to by the current line of the list variable. These two variables are independent instances of the Table Class and the use of a Data Object helps bind them together.

If we use an Object reference variable for instantiating our Data Object, there is another advantage over using just the Table Class features: we can more easily pass the Instance around to other parts of our application. A list based on a Table Class and then passed as a parameter spawns a new, independent instance of that Table Class, which may not be our intention.

If we are using native Omnis data manipulation in our application, Data Objects give us a reasonable equivalent to the facility of Table Classes. That is, we can have one central container for all the methods needed to retrieve, save and update records for a given table (File). We no long need to have this code duplicated across numerous windows and reports. We can simply create (or pass around) instances of the Data Object designated for handling those tasks for that File. Each instance acts as though it were a separate, intelligent segment of the Current Record Buffer.

But we can take this a step further...

Data Set Objects

In my view, an advantage of the Network database model over the Relational database model is the implementation of Data Sets in the Network Model. This is a collection of Record Sets (Tables) that are combined into a more complex entity, which can then be dealt with as a unit. Consider the combination of Invoice Header, Invoice Line Item, Product, Customer, Salesperson and Sales Tax District tables in an invoicing database application. Wouldn't it be nice if we could just take all of these tables together and define an entity we would call "Invoice"? Such an entity could point to an invoice number, for example, and completely populate itself with all the records in all these tables that relate to that invoice. In Network terminology, this is a Set Occurence made up of all the related Record Occurences that comprise that invoice.

While Omnis Studio does not allow us to set up such constructs in our database definition (although the use of Connections in the native Omnis database techniqe is a step in that direction), it allows us to set up such super-entities in a functional way in our applications using Object Classes. That is, we can set up a single Object Class that contains the methods and variables we need to manage a collection of related tables and the relationships among their records. We can then treat an instance of this Object as a Set Occurence in the Network Model sense. This is again true for both native Omnis-based and SQL-based databases.

In designing such an Object Class, we need to decide whether to simply use Row and/or List variables for each table involved in the Set or to use Object Classes we have already defined for these individual tables, but now house them inside our super-Object, which only needs to contain code to manage the interactions among those Objects. Yes, Object Classes can contain Object and Object reference instance variables, so we can nest these on multiple levels if we need to. Our Data Objects are then building blocks that we can combine into increasingly complex constructs to perform more interesting data manipulation tasks.

Object or Object Reference?

Beginning with Omnis Studio version 4.0, we now need to choose between using an Object or an Object reference variable for working with an Object Instance. This is not a blanket choice, but one we must make with each use of an Object. Our choice depends on whether we need to use the same object instance in multiple locations - on a window and in a report, for example. It also depends on whether we intend to store the Object directly in our database. Sometimes we have flexibility, but most of the time (like the storage issue) a choice will require that we use one or the other.

Often I will choose to use Object reference variables for Data Objects for a couple of reasons:

First, I most likely will want to pass the Objects around. I especially like to be able to pass an Object to a report for a quick print. But it is not strictly necessary to use Object reference variables for this, since the report is not likely to change anything contained in the Object Instance. But if I want to pass a Data Object to a second window which might allow the user to modify the contents of that Object, the Object reference data type is the obvious choice for such a variable.

Second, I can take advantage of the $construct method of the Object Instance. (Using the $new() method to populate an Object variable allows me to do this as well. But if I'm going to do that much work, I may as well just do $newref() instead and get the extra advantages of using an Object reference variable.) This allows me to pass parameters to the constructor, which can be used to then immediately populate my Data Object with appropriate data.

The only drawback to using the Object reference data type for a variable is that we must remember to populate that variable by using the $newref() method of the Object Class. So setting up an Object variable is a bit simpler, but the extra effort of setting up (and populating) an Object reference variable can be more rewarding.

In fact, the main time that I am dissuaded from using Object reference variables these days is if I choose to actually store an Object Instance in the database - which is a rare occurence. Direct storage of the Object requires using the Object data type for the variable. The Object reference data type is not available for defining a column in a File Class (another strong hint).

Next Time

In the next article, I will discuss External Objects - but the primary focus of that article will be on working with Object DAMs for access to SQL data sources. This will certainly not be a comprehensive treatment of SQL technique, but will focus on the use of Object and Object reference variables with these external resources.

 

 
© 2006 Copyright of the text and images herein remains with the respective author. No part of this newsletter may be reproduced, transmitted, stored in a retrieval system or translated into any language in any form by any means without the written permission of the author or Omnis Software.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7™ is a trademark of Omnis Software UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.

Search Omnis Developer Resources

 

Hit enter to search

X