Can Properties (DATA) be added dynamically to an Object (CLA

Post Reply
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Can Properties (DATA) be added dynamically to an Object (CLA

Post by xProgrammer »

Hi All

I can't see how to do it and I suspect it might well be impossible, particularly having looked at the code for TDatabase (which has a very clever, if not very pretty, workaround).

But before I spend too much time on alternatives, I thought that it was worth asking the question.

Thanks
Doug
(xProgrammer)
ShumingWang
Posts: 454
Joined: Sun Oct 30, 2005 6:37 am
Location: Guangzhou(Canton),China

Post by ShumingWang »

__ObjAddData(oyourbject,cnames)

Shuming Wang
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Thanks

That should make what I want to do so much easier and neater!

Regards
Doug
(xProgrammer)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Thanks Shuming

Works like a charm
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Doug,

Please notice that Class TDataBase does not add new DATAs.

It uses the Class "error handler" OnError() method to route the desired msgs to access "virtual" DATAs. So the same Class can be used to manage multiple different DBFs without having to inherit a new Class for each DBF :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi Antonio

I noticed that and now understand the reasoning behind it although I'm not convinced that it's strictly correct. If DATAs are added dynamically to an object (ie a class instance) they would presumably be independant of the class of that object - one class would still suffice wouldn't it?

In my architecture a class such as PATIENT doesn't inherit from a database class but instead contains a database object (or in reality a pointer to that object). The database object would ideally dynamically add properties not to the database object but to the calling object (in this case an object of class PATIENT).

Whilst I have not yet written this code I think it would look something like this in the PATIENT class constructor

Code: Select all

:oDBF:AddDatas( self )
Given the smarts I want to build into the database class that code might be a bit complicated but if one didn't want to support any translation of fieldnames etc it would be fiarly straight forward along the lines of:

Code: Select all

METHOD AddDatas( oCalling ) CLASS MyDataBaseClass

SELECT ::WorkArea
FOR ii := 1 TO FCOUNT()
   __ObjAddData( oCalling, FNAME( ii ) )
NEXT

RETURN nil
Doing a databsae read would be fiarly simple under these circumstances too with something like:

Code: Select all

METHOD ReadData( oCalling )

aVals := ARRAY( 0 )
aThis := ARRAY( 2 )
SELECT ::WorkArea
FOR ii := 1 TO FCOUNT()
   aThis[1] := FNAME( ii )
   aThis[2] := FIELDGET( ii )
   AADD( aVals, aThis )
NEXT
__ObjDataSetFromArray( oCalling, aVals )

RETURN nil
Please note that last call is wrong name - it exists but I don't have the documentation with me. Code is not optimised. Field Names could be preset once in the array aVals and not need to be reset each time. This code is to illustrate the principle only. oCalling would possibly be a property of the databse object and not need to be passed in each time.

Happy to receive comments / ideas / criticisms.

Regards
Doug
xProgrammer
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Doug,

If you add new DATAs, you add them to the Class not to the object,
so all that Class instantiated objects are affected :-(

Thats why the Class error handler approach is better, as it does not modify the original class.
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi Antonio

Thanks for the advice. The documentation I saw was a bit misleading in that it mostly talked about adding DATA to an object and the function is so named.

Certainly that would be a problem in some architectures, but I don't see it as an issue in mine. If I am adding the DATAs to a PATIENT class (using a reference to an object of that class) it surely won't affect my database class or other classes that contain objects of that database class?

Anyway I will certainly do some testing.

Actually I think that I will probably create a VIEW class, supporting multiple views for a table and presumably also support views across multiple tables.

Regards

Doug
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

As you said Antonio, __ObjAddData() adds DATAs to the class and not just the particular object it is called against. For my purposes that isn't a problem though.

It looks like I will end up with a RECORD class and a RECORD_LIST class.

Initial version will subclass these. Subclass will basically have a controlling array (set up by a series of #xcommands).

Subsequent version will not need subclassing but read parameters from a file.

I will be supporting:

data name conversion (UI independant of data storage) (in and out)
type conversion (in and out)
default values (for Blank() functionality)
field lengths (which may not be available from other modes of persistence)

Only just started but #xcommand version is looking like this:

Code: Select all

XLATE PT_KEY         TO sKey         AS PKEY 
XLATE PT_NMFAMLY    TO sNmFamly AS STRING LENGTH 32   
XLATE PT_NMGIVEN    TO sNmGiven AS STRING LENGTH 32  
XLATE PT_NMOTHER   TO sNmOther AS STRING LENGTH 32 
XLATE PT_NMPREV      TO sNmPrev  AS STRING LENGTH 32 
XLATE PT_NMPREF      TO sNmPref   AS STRING LENGTH 16  
XLATE PT_NMTITLE     TO sNmTitle   AS STRING LENGTH  8
XLATE PT_DOB           TO dDOB       AS DATE
XLATE PT_GENDER      TO cGender   AS CHAR 
XLATE PT_ADLINE1      TO sAdLine1   AS STRING LENGTH 32 
XLATE PT_ADLINE2      TO sAdLine2   AS STRING LENGTH 32 
XLATE PT_ADSUBRB    TO sAdSubrb  AS STRING LENGTH 24 
XLATE PT_ADSTATE     TO sAdState   AS STRING LENGTH  3 DEFAULT "NSW"
XLATE PT_ADPCODE    TO sAdPCode  AS STRING LENGTH  4 
XLATE PT_ADCNTRY     TO sAdCntry AS STRING LENGTH 24 
XLATE PT_PHHOME      TO sPhHome  AS STRING LENGTH 16 
XLATE PT_PHWORK      TO sPhWork  AS STRING LENGTH 16 
XLATE PT_PHMOB        TO sPhMob   AS STRING LENGTH 16 
XLATE PT_PHFAX          TO sPhFax    AS STRING LENGTH 16 
XLATE PT_EMAIL           TO sEmail    AS STRING LENGTH 24 
XLATE PT_MEDIC          TO sMedic    AS STRING LENGTH 10
XLATE PT_MEDPOS       TO sMedPos  AS CHAR
XLATE PT_VETAFF         TO sVetAff    AS STRING LENGTH 20 
XLATE PT_ACTIVE         TO cActive    AS CHAR 
XLATE PT_LUBY            TO sLUBy     AS SKEY
XLATE PT_LUWHEN       TO sLUWhen  AS DATETIME 
XLATE PT_LUACTN        TO cLUActn   AS CHAR
"AS STRING" is optional as it is the default. This is processed into an array along the following lines:

Code: Select all

#xcommand XLATE <fname> TO <pname> AS STRING LENGTH <length> => AADD( ::aPROPERTIES, { <"pname">, <"fname">, , "S", <length>, 0 } )
Since it is all controlled by an array conversion to being data driven should be straight forward.

Code generators were never quite adequate, but data driven is IMHO the way to go. I have done that in a Web based system (with a series of major commercial systems now using that technology). The thing that made it work where generators fail was the ability to include user defined script almost anywhere and the fact that everything was dynamic. xBase with its code blocks might be one of the few other systems where this approach could be as successful.

Regards
Doug
(xProgrammer)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Basic code is up and running. Doesn't yet cover all possibilities but I'm happy so far.

Method AddDatas performs following functions:

adds DATAs (not to it's class but to the class passed in)

writes field positions into the aPROPERTIES array (so that only has to be done once)

writes variable names into the aVALUES array (again so that only has to be done once)

It looks like this:

Code: Select all

METHOD AddDatas( oCalling ) CLASS VIEW

LOCAL aTHIS

oCalling:oDBF:Select()
oCalling:oDBF:GoTo( 100 )
::aVALUES := ARRAY( 0 )
FOR ii := 1 TO ::iProperties
   __ObjAddData( oCalling, ::aPROPERTIES[ii][1] )
   ::aPROPERTIES[ii][3] := FieldPos( ::aPROPERTIES[ii][2] )
   aTHIS := ARRAY( 2 )
   aTHIS[1] := ::aPROPERTIES[ii][1]
   AADD( ::aVALUES, aTHIS )
NEXT
Method ReadValues reads values from the data base table, translates the names, converts type as appropriate and sends the values to the object passed in. It looks like:

Code: Select all

METHOD ReadValues( oCalling ) CLASS VIEW

LOCAL cDataType

FOR ii = 1 TO ::iProperties
   cDataType := ::aPROPERTIES[ii][4]
   DO CASE
      CASE cDataType = "D"
         ::aVALUES[ii][2] := STOD( FieldGet( ::aPROPERTIES[ii][3] ) )
      OTHERWISE
         ::aVALUES[ii][2] := FieldGet( ::aPROPERTIES[ii][3] )
   ENDCASE
NEXT
__ObjSetValueList( oCalling, ::aVALUES )
Method BlankValues sets values in the passed in object to blanks or defaults as appropriate. It looks like:

Code: Select all

METHOD BlankValues( oCalling ) CLASS VIEW

LOCAL cDataType

FOR ii = 1 TO ::iProperties
   cDataType := ::aPROPERTIES[ii][4]
   DO CASE
      CASE cDataType = "C"
         ::aVALUES[ii][2] := " "
      CASE cDataType = "D"
         ::aVALUES[ii][2] := CTOD("  /  /    ")
      CASE cDataType = "P"
         ::aVALUES[ii][2] := "[NOT_YET_SET]"
      CASE cDataType = "Q"
         ::aVALUES[ii][2] := SPACE( 16 )
      CASE cDataType = "S"
         IF ::aPROPERTIES[ii][7] != nil
            ::aVALUES[ii][2] := ::aPROPERTIES[ii][7]
           ELSE
            ::aVALUES[ii][2] := SPACE( ::aPROPERTIES[ii][5] )
         ENDIF
      CASE cDataType = "T"
         ::aVALUES[ii][2] := SPACE( 16 )
   ENDCASE
NEXT
__ObjSetValueList( oCalling, ::aVALUES )
That just leaves method WriteValues which effectively does the reverse of ReadValues. It looks like:

Code: Select all

METHOD WriteValues( oCalling ) CLASS VIEW

FOR ii = 1 TO ::iProperties
   cDataType := ::aPROPERTIES[ii][4]
   DO CASE
      CASE cDataType = "D"
         FieldPut( ::aPROPERTIES[ii][3], DTOS( oSend( oCalling, ::aPROPERTIES[ii][1] ) ) )
      OTHERWISE
         FieldPut( ::aPROPERTIES[ii][3], oSend( oCalling, ::aPROPERTIES[ii][1] ) )
   ENDCASE
NEXT
This is basically operational code so I am convinced this approach will work. I think it may offer some advantages over other approaches.

xProgrammer
Post Reply