viernes, 14 de octubre de 2011

localmacro insanity

Sometimes writing a piece of legible and easily understood code just isn't enough. We have to obfuscate it all in the name of reusability. We have the Business Relationship form, which is as permissive an entity as you can get, and needed to 'separate' it between two types of relationships - legal and people. The client will see two designs and consequently will require two sets of business rules such as obligatory mandatory fields. All this of course generated from the same form.

Creating mandatory form fields on-the-fly requires us to both set the mandatory field to true on the datasource as well as physically adding code to the validateWrite method (link). For our particular use case we did this at form level rather than at the smmBusRelTable entity as we are bulk importing over 150,000 rows and the form will be the only point of access to the data. Despite all of that we must always try to put the buisiness logic at the table level if at all possible.

First of all in the init method of the form we could write the following piece of code:
smmBusRelTable_ds.object(dt.fieldName2Id('Name')).mandatory(true);
We would then repeat the code for the other fields depending upon the relationship type. However... We could use an inner function which would then avoid some repetition and would not confuse unfamiliar developers to the code base:
    DictTable dt = new DictTable(smmBusRelTable.TableId);
    
    void mandatory(str fieldName)
    {
        smmBusRelTable_ds.object(dt.fieldName2Id(fieldName)).mandatory(true);
    }
    ;
    
    switch (personTypeParam)
    {
        case EVE_PersonType::Organization:
            mandatory('Name');
            mandatory('NameAlias');
            mandatory('Phone');
            mandatory('EVE_DocumentType');
            mandatory('EVE_DocumentId');
            mandatory('EVE_DUPCI');
        break;

        case EVE_PersonType::Person:
            // More fields
        break;
    }
That isn't actually too bad. However as I indicated before we now need to perform checks in the validateWrite part of the form's smmBusRelTable datasource. It's not a real problem in my case as there are only a few fields but what about using the power of macros?

The next piece of code is in my opinion less legible/readable and we won't be able to debug/step through it, but I do like it! Lets throw away the previous code and start over. Firstly we've declared the field list of obligatory mandatory in the ClassDeclaration:
    // EVE_PersonType::Organization list of mandatory fields
    #localmacro.Organization
        %1(Name)
        %1(NameAlias)
        %1(Phone)
        %1(EVE_DocumentType)
        %1(EVE_DocumentId)
        %1(EVE_DUPCI)
    #endmacro

    // EVE_PersonType::Person list of mandatory fields
    #localmacro.Person
        %1(EVE_Name)
        %1(EVE_MiddleName)
        %1(EVE_Surname)
        %1(EVE_BirthDate)
        %1(EVE_Gender)
        %1(EVE_DocumentType)
        %1(EVE_DocumentId)
        %1(EVE_DUPCI)
        %1(Phone)
    #endmacro
You will notice an additional %1 and parenthesis around the field names. We'll be calling each of those fields with a function name passed in as an argument. When the #Person macro appears in our code we will be repeatingly call '%1(fieldName)' in the code for each line.

The form's init method will now call the below:
void setObligatoryControls()
{
    DictTable dt = new DictTable(smmBusRelTable.TableId);

    // Mandatory function fragment
    #localmacro.mandatory
        smmBusRelTable_ds.object(dt.fieldName2Id('%1')).mandatory(true);
    #endmacro
    ;

    switch (personType)
    {
        case EVE_PersonType::Organization:
            #Organization(#mandatory)
        break;

        case EVE_PersonType::Person:
            #Person(#mandatory)
        break;
    }
}
It's a similar approach within the validateWrite method:
public boolean validateWrite()
{
    boolean         ret;
    DictTable       dt = new DictTable(smmBusRelTable.TableId);
    
    // Mandatory function fragment
    #localmacro.mandatory
        if (!smmBusRelTable.%1)
        {   //Se debe rellenar el campo %1.
            ret = checkFailed(strfmt("@SYS110217", dt.fieldObject(dt.fieldName2Id('%1')).label()));
        }
    #endmacro
    ;

    ret = super();

    if (ret)
    {
        switch (personType)
        {
            case EVE_PersonType::Organization:
                #Organization(#mandatory)
            break;

            case EVE_PersonType::Person:
                #Person(#mandatory)
            break;
        }
    }

    return ret;
}
To get your head around it all, try reading the article in Axaptapedia that gave me the idea.

No hay comentarios:

Publicar un comentario