Il est souvent utile, voire indispensable, que les bases de données soient adaptées de manière souple et évolutive aux utilisateurs et à leurs activités dans le monde informatique. Le contrôle des données accessibles est également un sujet récurrent et sensible. De ce point de vue, les développeurs utilisent des méthodes et des formules parfois complexes pour donner ou restreindre l’accès aux informations, en fonction du contexte ou des droits d’accès des utilisateurs.
Prenons un exemple simple. Dans votre application, vous avez parfois besoin d’afficher une liste de personnes. Une des colonnes affiche leur nom complet, mais dans votre base de données, vous avez un champ prénom et un champ nom. Actuellement, vous écrivez une formule dans la colonne de la zone de liste, et vous devez gérer vous-même le tri sur la colonne. Ne serait-il pas génial d’avoir un champ calculé où vous pouvez définir sa formule de calcul et sa méthode de tri, et d’avoir toute la logique commerciale à l’intérieur de la classe et non dans chaque interface ?
Eh bien, à partir de 4D v19 R3, 4D fournit une solution à cela, avec des attributs calculés.
Attributs calculés de HDI ORDA
IMAGINEZ…
Vous avez une liste de personnes dont les noms, prénoms, dates de naissance, etc.
Imaginez que vous voulez afficher, dans un formulaire, une liste de personnes avec leur nom complet (basé sur le prénom + le nom de famille), leur âge (basé sur la date de naissance), leur photo actuelle (basée sur leur âge)…
Ensuite, dès qu’une personne est sélectionnée, vous pouvez afficher des informations sur ses parents, grands-parents, enfants, frères et sœurs, oncles et tantes, sans oublier les cousins et cousines !
Est-ce possible ? Oui, bien sûr.
Sans lignes de code compliquées dans le formulaire ? Oui !
Toute cette magie est possible grâce aux attributs calculés, sur lesquels il est également possible de rechercher et de trier, tout comme avec les autres attributs ! Voyons cela de plus près !
Définition et calculs
Pour expliquer la puissance de ce concept, nous allons commencer par un exemple concret. Une classe de données « personnes » contient classiquement des attributs tels que « nom », « prénom », « adresse », « code postal », « ville », « pays », « date de naissance », etc.
À partir de cette classe, nous pouvons avoir besoin du nom complet (nom de famille + prénom), de l’âge (basé sur la date de naissance) ou de l’adresse complète (en tant qu’objet).
Et nous pouvons même aller au-delà. Si les relations le permettent, les attributs calculés peuvent même être du type entité (par exemple, père ou mère) ou de la sélection d’entités (enfants, parents, grands-parents, frères et sœurs, etc.).
L’accès à ces attributs sera rendu possible par la classe « peopleEntity » dans laquelle de nouvelles fonctions doivent être définies à cet effet.
Ils seront appelés lorsque 4D aura besoin d’accéder à ces attributs, que ce soit pour les lire (ex. : affichage simple), lors d’une recherche ou d’un tri, et enfin lors d’une éventuelle sauvegarde après modification.
Le résultat des attributs calculés, comme l’adresse complète, peut nécessiter un autre attribut calculé, comme le nom complet. Ce type de récursion est également possible, nous y reviendrons.
Ces functions (get, set ainsi que query et orderBy qui seront discutés plus tard) doivent être définis à l’intérieur de la classe d’entité elle-même (ex : peopleEntity).
Accès aux attributs calculés
Obtenir
La première fonction, « get », permet de définir la manière dont l’attribut est calculé. Pour cela, elle doit retourner le résultat du calcul.
Cette fonction est la seule qui soit obligatoire pour utiliser les attributs calculés. En effet, c’est uniquement sa présence qui détermine l’existence même de cet attribut.
Function get fullName($event : Object) -> $result : Text
If (This.firstname=Null)
$result :=This.lastname
Else
If (This.lastname=Null)
$result :=This.firstname
Else
$result :=This.firstname+" "+This.lastname
End if
End if
Exposé, local … ou pas ?
Comment cet attribut sera-t-il accessible, et dans quelles conditions sera-t-il calculé ?
Le mot clé « exposed » qui peut être utilisé pour définir la fonction est l’équivalent de la case à cocher « Exposed » dans l’inspecteur de l’éditeur de structure.
Le mot-clé « local » utilisé en mode Client-Serveur détermine si le calcul doit être effectué systématiquement sur le serveur ou s’il doit être effectué localement pour limiter l’accès au réseau.
SET
La deuxième fonction, « set », permet l’opération inverse pour modifier les attributs réels à partir d’un attribut calculé.
Si nous prenons l’exemple de fullname, la règle simple sera de chercher un espace dans l’attribut pour redistribuer la valeur dans firstname et lastname. Bien sûr, il ne s’agit que d’un exemple simple. Si le nom complet contient plusieurs espaces, vous devrez appliquer une règle de gestion plus complexe, comme l’utilisation d’une barre oblique (/), et la traiter en priorité.
Par exemple « Pablo Miguel/de la Casa del Mar ».
L’avantage est qu’une fois définie, cette règle sera appliquée systématiquement sans que vous ayez à la redéfinir en fonction du contexte d’entrée.
Function set fullName($value: Text)
var $p : Integer
$p :=Position("/" ; $value)
If ($p>0)
$p :=Position(" " ; $value)
End if
If ($p>0)
This .firstname:=Substring($value; 1 ; $p-1)
This .lastname:=Substring($value; $p+1)
Else
This .firstname:= ""
This .lastname:=$value
End if
Un get sans set ? Et l’inverse…
Une fonction Get est obligatoire pour avoir accès à un attribut calculé. Une fonction Set est obligatoire pour rendre cet attribut modifiable, mais que faire si seule l’une ou l’autre de ces fonctions existe ?
Lorsque seule la fonction Get est disponible, l’attribut peut être considéré comme« en lecture seule » et ne peut être modifié.
Lorsque seul le Set est présent, l’attribut peut être écrit mais ne peut pas être relu. C’est le principe d’une boîte aux lettres ou d’un mot de passe.
Et ensuite ?
Les troisième et quatrième fonctions query et orderBy sont assez similaires et sont traitées de la même manière.
Ces fonctions ne sont pas obligatoires, mais leur utilisation augmentera drastiquement les performances, comme nous allons le voir. En effet, si ces fonctions ne sont pas définies, 4D devra exécuter la fonction « get » pour chaque entité et ensuite effectuer un tri séquentiel. Ceci risque d’appartenir et sera loin d’être optimisé.
Il est essentiel de comprendre et de se rappeler que les attributs calculés n’ont pas d’index. Pourtant, si leur calcul est basé sur des attributs, alors ces attributs peuvent (ou doivent) être indexés !
Pour cette raison, la possibilité est offerte de définir comment la recherche doit réellement être effectuée lorsqu’une requête ou un tri est lancé sur un attribut calculé.
Si l’on prend l’exemple de fullname, basé sur les attributs firstname et lastname (tous deux indexés), une recherche du type <<fullname = « Paul Smith » >> signifiera probablement que <<firstname = « Paul »>> et <<lastname = « Smith »>>. Vous êtes libre d’étendre la recherche et de décider qu’une recherche <<fullname = « Pa Sm » >> signifie que <<firstname commence par « Pa »>> et que <<lastname commence par « Sm »>>. Ou que << = « Martin » >> signifie que soit le nom de famille, soit le prénom commence par « Martin »….
Requête
Lors de l’exécution de la fonction « query » définie pour tout attribut calculé, le paramètre reçu contiendra deux propriétés nécessaires à la compréhension de la recherche souhaitée. Sachant cela, la requête peut être réécrite pour utiliser un (ou plusieurs) attribut(s) indexé(s) et ainsi éviter une recherche séquentielle.
$event.operator contient l’opérateur sous forme de chaîne (« == », « >= », « <« , etc.)
$event. Value contient la valeur à rechercher ou à comparer, également sous forme de chaîne de caractères.
Le résultat d’une fonction d’interrogation peut être soit une chaîne de caractères, soit un objet.
Si la fonction renvoie une chaîne de caractères, celle-ci doit être une requête valide.
$result:="nom de famille = A@ et prénom = B@"
Si la fonction renvoie un objet, celui-ci doit contenir deux propriétés :
- .query : une chaîne de requête valide qui peut contenir des caractères de remplacement (:1,:2, etc.)
- .parameters : une collection de valeurs à utiliser dans les caractères de remplacement.
$result:=New object("query" ; $query; "parameters" ; $parameters)
Voyons maintenant comment gérer la requête de classe de données suivante dans la fonction query !
$es:=ds.people.query("fullname = :1" ; "Paul Smith")
Voici le code complet de la fonction query :
Function query fullname ($event: Object) -> $result: Object
$fullname :=$event.value
$operator :=$event.operator
$p :=Position(" " ; $fullname)
If ($p>0)
$firstname :=Substring($fullname; 1 ; $p-1)+"@"
$lastname :=Substring($fullname; $p+1)+"@"
$parameters :=New collection($firstname; $lastname)
Else
$fullname :=$fullname+"@"
$parameters :=New collection($fullname)
End if
Case of
: ($operator= "==") | ($operator= "===")
If ($p>0)
$query := "prénom = :1 et nom = :2"
Else
$query := "prénom = :1 ou nom = :1"
End if
: ($operator=" !=")
If ($p>0)
$query :="prénom != :1 et nom de famille != :2"
Else
$query :="prénom != :1 et nom de famille != :1"
End if
End case
$result :=New object("query" ; $query; "parameters" ; $parameters)
Trier par
En ce qui concerne le tri, c’est assez similaire. Dans ce cas, trier les personnes par leur âge est exactement la même chose que de les trier par leur date de naissance inversée. Ainsi, lorsqu’un tri par âge est demandé, il sera astucieux d’utiliser la date de naissance, qui est indexée comme critère.
- Classement par échantillons :
$es:=ds.people.all().orderBy("age desc")
$es :=ds.people.all().orderBy("age asc")
- Fonction OrderBy :
Function orderBy age($event: Object) -> $result: String
If($event.operator = "desc")
$result:="birthday asc"
Else
$result :="birthday desc"
End if
Utilisation des index composites
Les index composites peuvent être utilisés de manière très positive dans certains cas. Rappel : Un tri par « nom de famille » et « prénom » n’ utilise pas l’index du nom de famille ou du prénom. Pour que ce type de tri soit optimisé, il faut qu’ il y ait un index nom précédent+prénom, ou prénom+nom… ou les deux.
Dans le cas d’un attribut calculé comme le nom complet, 4D utilisera cet index composite s’il est conscient qu’il s’agit en fait d’un tri nom + prénom ! Il est même possible de forcer un tri par nom de famille + prénom, même si le nom complet affiche le prénom avant le nom de famille !
- Trier par exemple :
Form.people:=ds.people.all().orderBy("nom complet asc")
- Fonction OrderBy :
Function orderBy fullname($event : Objet)->$result : Text
If ($event.descending)
$result := "nom desc, prénom desc"
Else
$result := "nom asc, prénom asc".
End if
Autres types possibles d’attributs COMPUTER
Dans les sections ci-dessus, nous avons couvert les attributs calculés de type scalaire (texte, numérique…), mais les attributs calculés peuvent aussi être des objets, des entités, ou des sélections d’entités !
Quelques exemples possibles :
- fullAddress : objet avec autant d’attributs que nécessaire (nom complet, rue, code postal, ville, pays, etc.)
Function get fullAddressThis($event: Object) -> $result: Object
$result :=New object
$result .fullName:=This.fullName // Another computed attribute can be used!
$result .address:=This.address
$result .zipCode:=This.zipCode
$result .
city:=This.city
$result.
state:=This.state
$result .
country:country
- bigBoss : entité
Function get bigBoss($event: Object) -> $result: cs.peopleEntity
$result :=this.manager.manager
- coworkers : entitéSelection
Function get coworkers($event: Object) -> $result: cs.peopleEntitySelection
$result :=this.manager.directReports.minus(this)
Conclusion
Lesattributs calculés sont nés et bien nés ! Ils apportent à la fois souplesse et puissance ainsi qu’un contrôle accru sur ce qui est accessible ou non. L’accès à ces attributs est optimisé de manière à ne pénaliser ni la mémoire ni l’accès au réseau. Ils sont la solution simple aux demandes des entreprises et répondent aux exigences accrues de la programmation moderne.
Pour plus de détails, lisez cette documentation.