Tutorial 3: Adding behavior and using Events

 

Until now, we have used our classes’ definitions just to hold data, but a complete object model not only has data but behavior.

 

You can enrich your object model by adding Methods and Events to your classes. You are free to add all the methods your classes need, because FoxDataObjects does not imposes any restriction to your business classes definitions, and you do not need to inherit your business classes from over-charged and over-engineered classes as other frameworks impose.

 

As a basic example, in the Category class we have a PictureBitMap property that holds a picture for the category instance in binary string format. We can add a couple of methods to the Category class that allows loading and saving such picture bitmap from/to a disk file. This way, a Category instance can work with BMP files, no matter if it is instantiated by a Visual FoxPro program, an ASP web page, a VB or .Net program, etc.

 

Let us add the LoadPic and SavePic methods to the Category class:

 

·         Clear your objects instances by typing this in the Command window:

 

 

CLEAR ALL

 

 

·         Open your Classes.PRG file to edit:

 

 

MODIFY COMMAND classes

 

 

 

·         Edit the Category class definition. It should end in something like:

 

 

DEFINE CLASS Category AS Custom

      CategoryName = ""

      Description = ""

      PictureBitMap = ""

     

      PROCEDURE LoadPic (tcFileName as String)

            IF VARTYPE(tcFileName)="C" AND FILE(tcFileName)

                  This.PictureBitMap = FILETOSTR(tcFileName)

            ENDIF

      ENDPROC

 

      PROCEDURE SavePic (tcFileName as String)

            IF VARTYPE(tcFileName)="C"

                  STRTOFILE(This.PictureBitMap,tcFileName)

            ENDIF

      ENDPROC

     

ENDDEFINE

 

This is just basic code. Best practices suggest that we should control and protect file operations within a Try/Catch block and return a logical value indicating whether the file was created or read successfully.

 

Your business objects will be plenty of methods encapsulating all the behavior your real life entities expose.

As with any other class, you can expose those methods by leaving them PUBLIC (as the sample) or, if they will be used only within your class or inherited class methods you can protect or hide them by adding the HIDDEN or PROTECTED keyword to the Method declaration (see DEFINE CLASS in the Visual FoxPro language reference).

 

You can freely use Assign_ and Access_ methods for your object instances to catch and react to changes on special properties, map stored values to a different subset or domain, etc. However, we will see such methods implemented in more advanced tutorials.

Using Persistence Events

 

When your applications use Persistent Services to save, retrieve or remove objects instances from the database, FoxDataObjects generates a rich set of events that can fire methods on your classes’ definitions.

 

It opens a new world of possibilities to your business objects.

 

Persistence Events give you the perfect space to implement Business Rules, whenever your objects are retrieved, removed or saved to the database.

 

In order for your objects instances to react to a persistence event, you just need to define a method in the class definition for the event you want to implement.

 

As an example, when an object instance is going to be saved to the database, FoxDataObjects generates an OnSave event. If your class has a method named OnSave, it is fired automatically by FoxDataObjects.

You could use the OnSave event on your class definition to execute validation code to enforce Business Rules, prepare or change data to be saved, update, remove other objects, send/retrieve data directly from the data store, etc.

 

When FoxDataObjects executes a Method in your class for a given Persistence Event, it passes one or more parameters to the Class method depending on the event fired.

The first parameter passed is a pointer to the Session object involved in the persistence event. This way you can access all the persistence services within your Method code.

Most events are conditional; it means that if the method implementing the event returns a False value, the whole operation is cancelled and the active transaction is rolled back.

Please check the Persistence Events section on the API Reference guide for detailed information.

 

Let us try some basic usage for Persistence Events. We will use the OnSave method on the Product class to check some conditions and impose a minimal set of business rules.

 

 

·         Edit the Product class definition. It should end in something like:

 

 

DEFINE CLASS Product AS Custom

      Code              = 0

      ProductName       = ""

      EnglishName       = ""

      Category          = .Null.

      QuantityInUnits = 0

      UnitPrice         = 0.0000

      UnitCost          = 0.0000

      UnitsInStock      = 0

      UnitsOnOrder      = 0

      ReorderLevel      = 0

      Discontinued      = .F.

     

      PROCEDURE OnSave(oSession as Object)

            IF EMPTY(This.Code)

                  oSession.Errors.Set("Product Code cannot be empty.")

                  RETURN .F.

            ENDIF

            IF This.UnitsInStock < This.ReorderLevel

                  This.AddToReorderList()

            ENDIF

      ENDPROC

     

      HIDDEN PROCEDURE AddToReorderList

      ENDPROC

 

ENDDEFINE

 

 

This is just basic code. We have added a method named OnSave that is fired by the Persistence Engine when the instance is going to be saved (Inserted/Updated) into the database. As any other persistence event, the method receives as a parameter a pointer to the current Session object, that we can use to access the whole set of functions and services exposed by this object.

In the example, we check for a not empty value present in the Code property. Otherwise, we use the session’s Errors structure to log a message and then return a False value. If the OnSave event returns a False value, the complete SaveObject persistence service is canceled and the underlying database transaction is rolled back.

We also test to know if the Product stock has dropped below the reorder value. In such case, we fire an internal method (hidden) that adds the product to a list of products to be re-ordered (we do not include that code her just to keep this basic sample clear).

 

Let us test our classes!

 

·         Close your Classes.PRG saving your changes

 

And try to save an empty Product instance:

 

Command

Result

 

SET PROCEDURE TO FDO, Classes ADDITIVE

 

 

 

oServer=CREATEOBJECT(“fdoServer”)

 

 

 

oSession=oServer.NewSession(“Tastrade.FDO”)

 

 

 

oPrd=CREATEOBJECT(“product”)

 

 

 

oPrd.ProductName=”Tropical Soda”

 

 

 

? oSession.SaveObject(oPrd)

 

 

.F.

 

? oSession.Errors.ToString()

 

 

 

Product Code cannot be empty.

 

 

oPrd.Code=2

 

 

 

 

? oSession.SaveObject(oPrd)

 

 

.T.

 

When we tried to save the Product instance, FoxDataObjects fired the OnSave method on the product class and got a false value as return value. Therefore, it canceled the operation and rolled back the transaction.

We used the Errors collection object to check what happened and we found our custom message logged. See the Errors collection section on the API Reference guide for a complete description on how you can use this powerful technique as the central communication mechanism among your application layers.

 

 

 

 


< Tutorial 2: Working with objects references

Home

Tutorial 4: Working with class inheritance >

 

 

Send feedback on this topic to RunAhead Technologies

For Technical support and product issues please contact us at support@foxdataobjects.com or visit http://www.foxdataobjects.com

 

Copyright (c) 2000-2005 RunAhead Technologies