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.
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:
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:
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.
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.
|