The mapping attribute is probably one of those features that many folks don’t know about. It doesn’t even get a lot of attention in the documentation, but it is a really handy feature that has saved my bacon more than once.
Before we can talk about the mapping attribute we need to first talk about homonymic copy, <= instead of =. If you look in the online help you will find both the mapping attribute and homonymic copy covered on the assignment operators page. When used with complex structures such as record buffers, structures, and classes, homonymic copy makes the assignment based on field name instead of position.
This can be very handy when you are trying to standardize field names in your project versus their actual names in the database. For example, it is especially handy if you are writing conversion code. We have also used it on occasion when we were presented with a database that used multiple tables for the same entity. For example multiple customer tables with different customized fields in them for different classes of customers, Yes sadly this is a real world example that we have seen more than once! We have been able to use mapping to sanitize and standardize the multiple sources into a common structure.
For today’s example, I am going to go over how I solved a recent requirement, that I am sure many of you have had similar needs, and show how the combination of the mapping attribute, class properties,s and the wxFileManger all combined to allow me to add a new feature to a project with very little actual coding involved. Although I will be showing some wxFileManager stuff and therefore some class stuff, most of this could be used and applied to whatever method of coding you are using. But we all know wxFileManger is the better way 😉
First a really quick, way oversimplified description of what the wxFileManger is and why it makes all of this so easy for me to change the project. At its heart the wxFileManager is just a fancy way to manage structured arrays, and if you look at the code we use homonymic copy to move things between these structures and the actual file buffers, and believe it or not deep down in the class we are even using the standard Hxxxxx commands for update records. So that means I already have a complex variable (the record class) that I can use the mapping attribute on, and the wxFileManger is doing homonymic copies so it will respect my mapping assignments.
On to the requirement. For a new consumer facing portal we have decided to allow the customers to log in with their mobile numbers as well as their email addresses. We already have a field in the database called Mobile, however, it unfortunately is a string field with no current validation in the interface so we have numbers writing like 3182307383, (318) 230-7383, 318-230-7383, 318.230.7383, +1 318-230-7383, and about as many other ways as you could imagine.
We need a clean standardized field that can be indexed and used to retrieve the users record using their mobile number. Cleaning up the field in the database, then changing the interface everywhere to format and display the number would be one approach but it would certainly be time and resource intensive, considering this project has 3 different websites and a windows application that all update the customer record. We decided instead to add a field to the database called DeformattedMobile, which stores the cleaned up 10 digit mobile number. A simple global procedure takes a string as input and returns a cleaned number
PROCEDURE cleanMobile(InNumber) CleanNumber is string // Get just the numbers FOR X = 1 _TO_ Length(InNumber) IF Asc(InNumber[[X]]) >= 48 AND Asc(InNumber[[X]]) <= 57 THEN CleanNumber += InNumber[[X]] END END // Strip leading 1 if included, assuming only Canada and US numbers IF CleanNumber[] = "1" THEN CleanNumber = CleanNumber[[2 TO Length(CleanNumber)]] END // Resulting Number must be 10 digits SWITCH Length(CleanNumber) CASE 10 RESULT CleanNumber CASE < 10 RESULT "" // less than 10 not a mobile so return nothing CASE > 10 RESULT CleanNumber[[1 TO 10]] // only 1st 10 are the number END
A quick conversion will get the database populated and we would be ready to go, except we still need to handle maintaining this field moving forward. Being an old school SQL guy, a trigger is always my first reaction to such a requirement, however, this is a HFSQL database and I am not fond of triggers with HFSQL. No need to discuss it further in this article, just suffice to say a trigger was ruled out for this solution.
However, since I am using the wxFileManger for this project I have the opportunity to use class properties. Those coming from other languages may recognize these as setters and getters. If the Mobile were a class property, instead of a member, then in the assignment (Set) property I can also update the DeformattedMobile field. And very similar to a trigger, anytime I update Mobile, like magic DeformattedMobile will also be updated.
The challenge this solution presents is Mobile is a class member, and needs to be so it can be moved back and forth between the record buffer and the class. And you probably guessed it by now, the mapping attribute comes to my rescue.
First I removed Mobile as being a member, then I added a Local Private section to the definition and added DeformattedMobile and Mobile, both with a prefix of c (wxPert’s standard for noting fields that are only for using within the class), and mapping them to their appropriate database fields names. I use a Local Private section because I don’t want these fields exposed outside the class, all access to them for the rest of the project will be via the properties we are about to create.
At this point when reading a record from the database via the wxFileManager cDeformattedMobile and cMobile will be populated with the appropriate values from the database. However as mentioned above since they are in a Local Private section they won’t be visible outside the class, so time to create some properties.
For those that haven’t taken the plunge into classes yet, I describe properties as functions that can be referenced as variables. This means it can be used anywhere a variable can be used, even bound to screen controls, and whenever it is referenced the property code (function) will be ran.
First I create a Read (Get) property called DeformattedMobile.
As you can see all the code does is return our cDeformattedMobile we declared above as private. Now anywhere outside of the class DeformatedMobile can be seen, but since I didn’t create a Write (Set) property for it it can’t be updated outside the class. I don’t want any errant code written that could possibly corrupt the value in DeformattedMobile, I always want its value populated based on the value of Mobile
Now I create both a Get and a Set for Mobile
Just as with the Get for DeformattedMobile, all the get is doing is returning the private variable defined earlier, in this case, Mobile
The Set Property is where we get a bit fancier, first it accepts a parameter (Value) this is standard for all Set properties. And I am using that value to update the private member cMobile, which in turn, thanks to the mapping attribute, will update the Mobile field in the database. Clear as mud right 😉
But I also am updating the cDeformattedMobile private member using the cleanMobile procedure and the Value. So now anytime Mobile is updated, DeformattedMobile is automagically updated as well!
And like that I am done, I don’t need to touch any code anywhere else in the project. Because all of the existing code was using the member named Mobile, which is now a property, it will all work as before except it will not also maintain the DeformattedMobile field.
Not counting the cleanMobile procedure we are talking about 10 lines of code! 3 in the class declaration area, and 7 in the properties area, to completely satisfy the requirement and not have to worry about any interface code not getting updated correctly, etc.
If this type of example doesn’t convince you to take a harder look at classes and perhaps even the wxFileManger then I don’t know what would! This is what they mean by 10 times faster!!!