clir-data_manager 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +44 -0
- data/Manual/Manuel_fr.md +784 -0
- data/Manual/Manuel_fr.pdf +0 -0
- data/README.md +279 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/clir-data_manager.gemspec +34 -0
- data/lib/clir/data_manager/Displayer.rb +30 -0
- data/lib/clir/data_manager/Editor.rb +208 -0
- data/lib/clir/data_manager/Manager.rb +1184 -0
- data/lib/clir/data_manager/Periode.rb +366 -0
- data/lib/clir/data_manager/PrecedencedList.rb +98 -0
- data/lib/clir/data_manager/Property.rb +438 -0
- data/lib/clir/data_manager/Validator.rb +157 -0
- data/lib/clir/data_manager/constants.rb +14 -0
- data/lib/clir/data_manager/errors_and_messages.rb +123 -0
- data/lib/clir/data_manager/module_constants.rb +13 -0
- data/lib/clir/data_manager/version.rb +5 -0
- data/lib/clir/data_manager.rb +21 -0
- metadata +114 -0
data/Manual/Manuel_fr.md
ADDED
@@ -0,0 +1,784 @@
|
|
1
|
+
# Manuel français de `Clir::DataManager`
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
[TOC]
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
## Présentation
|
10
|
+
|
11
|
+
Cette classe permet de gérer facilement les données des instances (et des classes) dans les applications en ligne de commande (donc les applications tournant dans le Terminal).
|
12
|
+
|
13
|
+
Il suffit, pour une classe donnée, de définir les propriétés de ses instances en respectant quelques règles pour pouvoir ensuite afficher, créer, éditer et supprimer n'importe quelle valeur de ces instances.
|
14
|
+
|
15
|
+
Par exemple :
|
16
|
+
|
17
|
+
On définit les données de la classe `MaClasse` :
|
18
|
+
|
19
|
+
~~~ruby
|
20
|
+
require 'clir/data_manager'
|
21
|
+
|
22
|
+
class MaClasse
|
23
|
+
include ClirDataManagerConstants
|
24
|
+
DATA_PROPERTIES = [
|
25
|
+
{prop: :id, type: :integer, specs:REQUIRED|DISPLAYABLE}, valid_if: {uniq: true},
|
26
|
+
{
|
27
|
+
prop: :name,
|
28
|
+
type: :string,
|
29
|
+
specs:ALL_SPECS,
|
30
|
+
valid_if: {
|
31
|
+
not_empty:true,
|
32
|
+
min_length: 3,
|
33
|
+
max_length:40
|
34
|
+
}
|
35
|
+
}
|
36
|
+
]
|
37
|
+
|
38
|
+
# On doit définir aussi la sauvegarde
|
39
|
+
@@save_system = :card # données sauvées dans des fiches
|
40
|
+
@@save_format = :yaml # au format YAML
|
41
|
+
@@save_location = "./data" # dans le dossier 'data'
|
42
|
+
end
|
43
|
+
~~~
|
44
|
+
|
45
|
+
On instancie un manager de données pour la classe :
|
46
|
+
|
47
|
+
~~~ruby
|
48
|
+
Clir::DataManager.new(MaClasse)
|
49
|
+
~~~
|
50
|
+
|
51
|
+
Cela implémente automatiquement plein de méthodes utiles pour les données et notamment :
|
52
|
+
|
53
|
+
~~~ruby
|
54
|
+
MaClasse.get(id)
|
55
|
+
# Retourne l'instance d'identifiant :id
|
56
|
+
|
57
|
+
<MaClasse>#create([{data}])
|
58
|
+
# Pour créer une instance (avec ou sans les données {data}
|
59
|
+
|
60
|
+
MaClasse.items
|
61
|
+
# => liste des instances
|
62
|
+
|
63
|
+
MaClasse.table
|
64
|
+
# => table des instances avec en clé leur identifiant
|
65
|
+
~~~
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
Note : si la classe définissait ses données dans une autre constante que `DATA_PROPERTIES`, il faudrait donner cette constante en second argument de `Clir::DataManager.new`.
|
70
|
+
|
71
|
+
---
|
72
|
+
|
73
|
+
On peut maintenant utiliser cette méthode pour éditer les valeurs d'une instance :
|
74
|
+
|
75
|
+
~~~ruby
|
76
|
+
inst = MaClasse.new
|
77
|
+
|
78
|
+
inst.create
|
79
|
+
# => Permet de créer l'instance
|
80
|
+
|
81
|
+
inst.edit
|
82
|
+
# => permet d'éditer (de modifier) l'instance
|
83
|
+
|
84
|
+
inst.display
|
85
|
+
# => Affiche les données de l'instance
|
86
|
+
|
87
|
+
inst.remove
|
88
|
+
# => Détruit l'instance
|
89
|
+
|
90
|
+
inst.new?
|
91
|
+
# => true si c'est une création d'instance (i.e. une instance qui n'a
|
92
|
+
# pas encore été enregistrée
|
93
|
+
|
94
|
+
~~~
|
95
|
+
|
96
|
+
---
|
97
|
+
|
98
|
+
## Méthodes `after_` et `before_`
|
99
|
+
|
100
|
+
On peut programmer pour l’instance des méthodes à appeler avant (`before_`) ou après (`after_`) les opérations ci-dessus. On trouve donc :
|
101
|
+
|
102
|
+
* **`before_create`** sera appelée avant la création si elle existe. Elle peut retourner les données à prendre en compte,
|
103
|
+
* **`after_create`** sera appelée (sans argument) après la création complète de l’instance,
|
104
|
+
* **`before_edit`** sera appelée (sans argument) avant l’édition si elle existe (noter que la création appelle aussi cette méthode),
|
105
|
+
* **`after_edit`** sera appelée (sans argument) après l’édition et l’enregistrement de l’instance,
|
106
|
+
* **`before_display`** sera appelée avec les options en premier argument (optionnellement) avant l’affichage. Elle retournera optionnellement les options modifiées,
|
107
|
+
* **`after_display`** sera appelée (sans argument) après que l’instance a été affichée (donc, quand elle est affichée,
|
108
|
+
* **`before_remove`** sera appelée (sans argument) avant la destruction de l’instance,
|
109
|
+
* **`after_remove`** sera appelée (sans argument) après la destruction de l’instance (“instance”, ici, signifie ses données enregistrées, car l’instance en tant qu’objet informatique abstrait existe toujours)
|
110
|
+
|
111
|
+
---
|
112
|
+
|
113
|
+
<a name="data-properties"></a>
|
114
|
+
|
115
|
+
## Données des propriétés d’instance (`DATA_PROPERTIES`)
|
116
|
+
|
117
|
+
Le bon fonctionnement du ***manager de propriété*** tient principalement à la bonne définition des propriétés, généralement (mais pas exclusivement) dans la constante **`DATA_PROPERTIES`**. Cette définition permet de tout savoir sur les données et de savoir comment les gérer.
|
118
|
+
|
119
|
+
### Liste `Array`
|
120
|
+
|
121
|
+
Les données sont définies dans une liste (`{Array}`) afin de préserver l'ordre défini.
|
122
|
+
|
123
|
+
### Nom de la constante
|
124
|
+
|
125
|
+
Par défaut, le gem s'attend à trouver la constante **`DATA_PROPERTIES`** définie par la classe à manager. Mais ces données peuvent être définies dans toute autre constante si elle est fournie en second argument de l'instanciation du manager :
|
126
|
+
|
127
|
+
~~~ruby
|
128
|
+
class MaClasse
|
129
|
+
AUTRES_DATA = [...]
|
130
|
+
def self.manager
|
131
|
+
@@manager ||= Clir::DataManager.new(self, AUTRES_DATA)
|
132
|
+
end
|
133
|
+
~~~
|
134
|
+
|
135
|
+
---
|
136
|
+
|
137
|
+
<a name="attributs-property"></a>
|
138
|
+
|
139
|
+
## Attributs d’une propriété
|
140
|
+
|
141
|
+
Comme nous venons de le voir, chaque ligne (chaque item) de [`DATA_PROPERTIES`](#data-properties) définit une propriété de l’instance, comme on les appelle en ruby. Nous allons voir les arguments que doivent ou peuvent comporter chaque *property*.
|
142
|
+
|
143
|
+
<a name="attributs-list"></a>
|
144
|
+
|
145
|
+
### Liste des attributs
|
146
|
+
|
147
|
+
* [**`:prop`**](#attribut-prop)
|
148
|
+
* [**`:name`**](#attribut-name)
|
149
|
+
* [**`:short_name`**](#attribut-short-name)
|
150
|
+
* [**`:type`**](#attribut-type)
|
151
|
+
* [**`:specs`**](#attribut-specs)
|
152
|
+
* [**`:default`**](#attribut-default)
|
153
|
+
* [**`:values`**](#attribut-values)
|
154
|
+
* [**`:values_filter`**](#attribut-values_filter)
|
155
|
+
* [**`:itransform`**](#attribut-itransform) (transformation immédiate de la valeur entrée)
|
156
|
+
* [**`:mformat`**](#attribut-mformat) (transformation de la valeur pour affichage)
|
157
|
+
|
158
|
+
---
|
159
|
+
|
160
|
+
<a name="attribut-prop"></a>
|
161
|
+
|
162
|
+
### `:prop` : Nom de la propriété, requise
|
163
|
+
|
164
|
+
`:prop` définit le nom de la propriété, pour savoir si c’est l’identifiant, le nom, la date de début etc.
|
165
|
+
|
166
|
+
La première propriété, dans *Data-Manager*, doit obligatoirement définir la propriété `:id` :
|
167
|
+
|
168
|
+
~~~ruby
|
169
|
+
DATA_PROPERTIES = [
|
170
|
+
{prop: :id ... }
|
171
|
+
]
|
172
|
+
~~~
|
173
|
+
|
174
|
+
#### Nom de propriété spéciale : `:<class min>_id`
|
175
|
+
|
176
|
+
Un nom de propriété spécial — et même magique — est constitué d’un nom minusculisé de classe connue suivi par `_id`. Quand un nom est composé de la sorte, cela signifie que la propriété concernant l’identifiant d’une autre instance dans la classe est minusculisée.
|
177
|
+
|
178
|
+
Par exemple :
|
179
|
+
|
180
|
+
~~~ruby
|
181
|
+
class MaClasseManaged
|
182
|
+
DATA_PROPERTIES = [
|
183
|
+
# ...
|
184
|
+
{prop: user_id ,name:"Propriétaire" ,type: :id ...}
|
185
|
+
]
|
186
|
+
end
|
187
|
+
~~~
|
188
|
+
|
189
|
+
… signifie que chaque instance de cette classe peut être liée à une instance de la classe `User`.
|
190
|
+
|
191
|
+
*DataManager* aussitôt lui-même ce genre de propriété, en :
|
192
|
+
|
193
|
+
* utilisant la méthode générale `choose` pour définir cette donnée,
|
194
|
+
* implémentant la méthode-propriété `classe_min`qui retournera l’instance en question.
|
195
|
+
|
196
|
+
**Minusculisation du nom de la classe**
|
197
|
+
|
198
|
+
Si le nom de la classe contient des capitales, il sera décamélisé (`DataManager` => `data_manager` => `data_manager_id`).
|
199
|
+
|
200
|
+
Si le nom de la classe est composé, les doubles doubles-points seront remplacés aussi par des tirets plats (`Edic::Produit` => `edic_produit` => `edic_produit_id`
|
201
|
+
|
202
|
+
<a name="liste-elements"></a>
|
203
|
+
|
204
|
+
#### Propriété spéciale : `<classe min>_ids`
|
205
|
+
|
206
|
+
Similaire à la classe précédente, mais pour une liste d’éléments. Le type est alors `:ids`
|
207
|
+
|
208
|
+
~~~ruby
|
209
|
+
DATA_PROPERTIES = [
|
210
|
+
# ...
|
211
|
+
{prop: espace_nom_user_ids, name: "Partitipantes", type: :ids, values_filter: {sexe: 'F'}, ...}
|
212
|
+
]
|
213
|
+
~~~
|
214
|
+
|
215
|
+
Le code ci-dessus permet de choisir une liste de femmes (de “participantes”).
|
216
|
+
|
217
|
+
---
|
218
|
+
|
219
|
+
<a name="espace-de-nom"></a>
|
220
|
+
|
221
|
+
#### Espace de nom en question
|
222
|
+
|
223
|
+
Attention : si la classe relative se trouve dans un espace de nom, même si c’est le même espace de nom que la classe en question, il faut indiquer cet espace de nom dans `:prop` :
|
224
|
+
|
225
|
+
~~~ruby
|
226
|
+
module EspaceNom
|
227
|
+
class User
|
228
|
+
# ...
|
229
|
+
end
|
230
|
+
|
231
|
+
class MaClasseManaged
|
232
|
+
DATA_PROPERTIES = [
|
233
|
+
# ...
|
234
|
+
{prop: espace_nom_user_id , name: "Propriétaire" ,type: :id ...}
|
235
|
+
]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
~~~
|
239
|
+
|
240
|
+
De la même manière, il faut noter que la propriété-méthode qui sera créé, pour récupérer l’instance, comportera cet espace de nom. Donc, dans le programme il faudra utiliser :
|
241
|
+
|
242
|
+
~~~ruby
|
243
|
+
instance.espace_nom_user
|
244
|
+
# => instance EspaceNom::User
|
245
|
+
~~~
|
246
|
+
|
247
|
+
Pour simplifier le code, on peut définir la classe managée de cette manière :
|
248
|
+
|
249
|
+
~~~ruby
|
250
|
+
require 'clir/data-manager'
|
251
|
+
|
252
|
+
class MaClasseManaged
|
253
|
+
DATA_PROPERTIES = [
|
254
|
+
# ...
|
255
|
+
{prop: espace_user_id: name: "Propriétaire", type: :id}
|
256
|
+
]
|
257
|
+
end #/class
|
258
|
+
|
259
|
+
Clir::DataManager.new(MaClasseManaged)
|
260
|
+
|
261
|
+
class MaClasseManaged
|
262
|
+
|
263
|
+
|
264
|
+
alias :user :espace_user
|
265
|
+
# et : alias :user_id :espace_user_id
|
266
|
+
|
267
|
+
end
|
268
|
+
~~~
|
269
|
+
|
270
|
+
De cette manière on pourra facilement utiliser :
|
271
|
+
|
272
|
+
~~~ruby
|
273
|
+
item = MaClasseManaged.new
|
274
|
+
item.user
|
275
|
+
# => Instance User de l'item
|
276
|
+
~~~
|
277
|
+
|
278
|
+
|
279
|
+
|
280
|
+
---
|
281
|
+
|
282
|
+
<a name="attribut-name"></a>
|
283
|
+
|
284
|
+
### Attribut `:name`
|
285
|
+
|
286
|
+
Nom humain de la propriété, qui sera utilisé en label pour afficher les données de l’instance. C’est forcément un `String` (TODO: pour le moment en tout cas, car on pourrait imaginer que ça soit une procédure à évaluer en fonction de l’instance).
|
287
|
+
|
288
|
+
---
|
289
|
+
|
290
|
+
<a name="attribut-short-name"></a>
|
291
|
+
|
292
|
+
### Attribut `:short_name`
|
293
|
+
|
294
|
+
Nom humain court de la propriété, spécialement désigné pour être utilisé dans l’affichage par table.
|
295
|
+
|
296
|
+
---
|
297
|
+
|
298
|
+
<a name="attribut-type"></a>
|
299
|
+
|
300
|
+
### Attribut `:type`
|
301
|
+
|
302
|
+
#### Liste des types
|
303
|
+
|
304
|
+
~~~bash
|
305
|
+
:string Une chaine quelconque
|
306
|
+
:number Un nombre quelconque
|
307
|
+
:select Valeur dans une liste (fournie par :values)
|
308
|
+
:date Date de type JJ/MM/AAAA
|
309
|
+
:mail Un email valide
|
310
|
+
:id ID d’une instance connue
|
311
|
+
:ids Liste d’IDs d’instance connues
|
312
|
+
:prix Un prix (nombre flottant)
|
313
|
+
:people Une ou des personnes
|
314
|
+
:url Une URL existante
|
315
|
+
~~~
|
316
|
+
|
317
|
+
---
|
318
|
+
|
319
|
+
<a name="attribut-specs"></a>
|
320
|
+
|
321
|
+
### Attribut `:specs`
|
322
|
+
|
323
|
+
Spécificité de la propriété, permet de savoir si elle est requise (`REQUIRED`), éditable (`EDITABLE`), affichable (`DISPLAYABLE`), supprimable (`REMOVABLE`) ou toutes ces propriétés en même temps (`ALL`).
|
324
|
+
|
325
|
+
~~~bash
|
326
|
+
REQUIRED # La propriété doit impérativement être définie
|
327
|
+
EDITABLE # La propriété peut être éditée. L'ID par exemple,
|
328
|
+
# ne peut pas l'être. Il est défini programmatiquement
|
329
|
+
# et ne peut être modifié
|
330
|
+
DISPLAYABLE # La propriété est affichée dans l'affichage complet de
|
331
|
+
# la donnée
|
332
|
+
TABLEIZABLE # La propriété est affichée dans l'affichage de la donnée
|
333
|
+
# dans une table (Clir::Table). Dans ce cas, il peut être
|
334
|
+
# intéressant de définir :short_name pour le libellé de
|
335
|
+
# l'entête.
|
336
|
+
REMOVABLE # La propriété peut être détruite (remplacée par nil)
|
337
|
+
ALL # Toutes les précédentes
|
338
|
+
~~~
|
339
|
+
|
340
|
+
|
341
|
+
|
342
|
+
---
|
343
|
+
|
344
|
+
<a name="attribut-default"></a>
|
345
|
+
|
346
|
+
### Attribut `:default`
|
347
|
+
|
348
|
+
Valeur par défaut. Elle peut être définie par :
|
349
|
+
|
350
|
+
* une valeur explicite (d’un type correspondant au type de la propriété),
|
351
|
+
|
352
|
+
* une procédure de calcul qui reçoit en premier argument l’instance courante,
|
353
|
+
|
354
|
+
~~~ruby
|
355
|
+
{prop: first_job_year, ... , default: Proc.new { |inst| inst.naissance + 18 }
|
356
|
+
~~~
|
357
|
+
|
358
|
+
Noter que cette procédure peut retourner un `Symbol` qui pourra alors être considéré (ou pas) comme méthode de l’instance ou de la classe.
|
359
|
+
|
360
|
+
* un `Symbol` renvoyant à une méthode de l’instance OU une méthode de la classe de l’instance :
|
361
|
+
|
362
|
+
~~~ruby
|
363
|
+
{prop: first_job_year, ... , default: :year_majorite}
|
364
|
+
# En considérant que :year_majorite est une méthode d'instance de la
|
365
|
+
# classe, qui retourne l'année de majorité de l'individu
|
366
|
+
~~~
|
367
|
+
|
368
|
+
|
369
|
+
|
370
|
+
---
|
371
|
+
|
372
|
+
<a name="attribut-values"></a>
|
373
|
+
|
374
|
+
### Attribut `:values`
|
375
|
+
|
376
|
+
S'emploie pour un type `:select`.
|
377
|
+
|
378
|
+
---
|
379
|
+
|
380
|
+
<a name="attribut-values_filter"></a>
|
381
|
+
|
382
|
+
### Attribut `:values_filter`
|
383
|
+
|
384
|
+
Filtre à utiliser pour les `:values` en fonction de l'instance.
|
385
|
+
|
386
|
+
Peut s'utiliser pour les types `:select` et les `:prop` de type `:<class min>_ids` (type `:ids`).
|
387
|
+
|
388
|
+
Cette valeur doit être une table (un dictionnaire) qui contient en clé les propriétés qui devront être filtrées et en valeur le filtrage à appliquer. Par exemple :
|
389
|
+
|
390
|
+
~~~ruby
|
391
|
+
{values: clients, values_filter: {sexe: 'F'} ...}
|
392
|
+
~~~
|
393
|
+
|
394
|
+
… filtrera uniquement les clientes, puisqu’on cherchera dans les clients les instances dont la valeur `:sexe` est à `“F"`.
|
395
|
+
|
396
|
+
La valeur de la propriété peut être aussi un `Symbol`. C’est alors une propriété de l’instance. Un cas typique est le cas d’une facture. Une facture est donc une instance qui rassemble plusieurs achats d’un client (donc plusieurs ventes). Pour pouvoir choisir les ventes, on ne doit afficher que les achats du client. On utilise donc toutes les ressources de *DataManager* en définissant la propriété qui va consigner les identifiants des achats de cette manière :
|
397
|
+
|
398
|
+
~~~ruby
|
399
|
+
class Facture
|
400
|
+
|
401
|
+
DATA_PROPERTIES = [
|
402
|
+
{prop: client_id, type: id, name: "Client"},
|
403
|
+
{prop: vente_ids, type: ids: name: "Achats concernés",
|
404
|
+
values_filter: {client_id: :client_id}
|
405
|
+
]
|
406
|
+
|
407
|
+
end
|
408
|
+
~~~
|
409
|
+
|
410
|
+
**EXPLICATION**
|
411
|
+
|
412
|
+
La première propriété, `:client_id`, de type `:id`, permet de choisir automatiquement un client dans la liste des clients. Elle prend comme valeur l’identifiant dudit client.
|
413
|
+
|
414
|
+
La seconde propriété, `:vente_ids`, de type `:ids`, permet de consigner les ventes concernées par la facture. Pour ne pas afficher toutes les ventes, mais seulement celles du client, le filtre `{client_id: client_id}` signifie : “ne prendre que les ventes dont le `client_id` (clé du filtre) n’est égal à la valeur `:client_id` de la propriété de la facture.
|
415
|
+
|
416
|
+
La **clé** correspond dont à une propriété de la chose concernée (ici les ventes)
|
417
|
+
|
418
|
+
La **valeur** correspond à une propriété de l’instance en édition (ici la facture).
|
419
|
+
|
420
|
+
---
|
421
|
+
|
422
|
+
<a name="attribut-valid_if"></a>
|
423
|
+
|
424
|
+
### Attribut`:valid_if`
|
425
|
+
|
426
|
+
Définition une procédure de validation de la donnée particulière. Peut être :
|
427
|
+
|
428
|
+
* une **Procédure (`Proc`)**. Elle est appelée avec en premier argument la nouvelle valeur et en second argument l’instance de l’objet traité,
|
429
|
+
* un **`Symbol`** peut être :
|
430
|
+
* une méthode (prédicate) appliqué à la valeur elle-même. Par exemple `numeric?`,
|
431
|
+
* une méthode d’instance de l’objet traité. Elle reçoit en premier argument la nouvelle valeur testée,
|
432
|
+
* une méthode de classe de l’objet traité. Elle reçoit les mêmes argument que la procédure (nouvelle valeur, instance).
|
433
|
+
|
434
|
+
Toutes ces méthodes doivent retourner `nil` si la nouvelle donnée est valide, ou le message d’erreur en cas de problème.
|
435
|
+
|
436
|
+
---
|
437
|
+
|
438
|
+
<a name="attribut-itransform"></a>
|
439
|
+
|
440
|
+
### Attribut `:itransform`
|
441
|
+
|
442
|
+
Transformation immédiate de la valeur entrée par l'utilisateur. Peut-être une `Proc` ou un `Symbol`.
|
443
|
+
|
444
|
+
Cf. [Formatage des données](#formatage-affichage).
|
445
|
+
|
446
|
+
---
|
447
|
+
|
448
|
+
<a name="attribut-mformat"></a>
|
449
|
+
|
450
|
+
### Attribut `:mformat`
|
451
|
+
|
452
|
+
Définition de la méthode de transformation de la donnée à l'affichage. Peut-être une `Proc` ou un `Symbol`.
|
453
|
+
|
454
|
+
Répond aux mêmes critères que [`itransform`](#attribut-itransform) mais s'applique seulement à l'affichage de la donnée.
|
455
|
+
|
456
|
+
Cf. [Formatage des données](#formatage-affichage).
|
457
|
+
|
458
|
+
---
|
459
|
+
|
460
|
+
<a name="attribut-if"></a>
|
461
|
+
|
462
|
+
### Attribut `:if`
|
463
|
+
|
464
|
+
L’attribut `:if` permet de déterminer si la propriété doit appartenir à l’instance, doit être défini. Par exemple, pour un certain produit il peut exister des propriétés spéciales qui n’appartiennent qu’à lui.
|
465
|
+
|
466
|
+
Peut être :
|
467
|
+
|
468
|
+
* un procédure (`Proc`),
|
469
|
+
* une méthode de l’instance (`Symbol`),
|
470
|
+
* une valeur booléenne explicite.
|
471
|
+
|
472
|
+
Cet attribut peut être une `Proc`édure. Cette procédure reçoit en argument l’instance et doit retourner `true` ou `false`. Par exemple :
|
473
|
+
|
474
|
+
~~~ruby
|
475
|
+
{prop: :cover, ... , if: Proc.new { |inst| inst.type == 'livre' }
|
476
|
+
~~~
|
477
|
+
|
478
|
+
Dans le cas ci-dessus, la propriété `:cover` définissant la couverture ne sera défini (éditable, affichable) que si la propriété `type`du produit est ‘livre’.
|
479
|
+
|
480
|
+
L’attribut peut être également une méthode (prédicate certainement) de l’instance. C’est alors un `{Symbol}`.
|
481
|
+
|
482
|
+
~~~ruby
|
483
|
+
{prop: :cover, ... , if: :livre?}
|
484
|
+
# => Seulement si <instance>.livre? retourne true
|
485
|
+
~~~
|
486
|
+
|
487
|
+
|
488
|
+
|
489
|
+
|
490
|
+
---
|
491
|
+
|
492
|
+
### Nom humain utilisé pour l’affichage de l’instance
|
493
|
+
|
494
|
+
Il y a 5 façons différents pour définir le nom (`:name` dans `Tty-prompt`) qui sera utilisé pour désigner l’instance :
|
495
|
+
|
496
|
+
* **valeur par défaut**. Par défaut, il s’agit de la **deuxième propriété définie** (entendu que la première est l’identifiant),
|
497
|
+
|
498
|
+
* **valeur générale**. Dans la classe, si on définit la **méthode d’instance #name4tty**, c’est cette méthode qui sera appelée et devra retourner le nom à afficher. Typiquement, si la classe est `People`, qui définit les propriétés `:nom` et `:prenom`, on pourra avoir une méthode :
|
499
|
+
|
500
|
+
~~~ruby
|
501
|
+
class MaClasseManaged
|
502
|
+
def name4tty
|
503
|
+
"#{prenom} #{nom}".strip
|
504
|
+
end
|
505
|
+
end
|
506
|
+
~~~
|
507
|
+
|
508
|
+
* **nom de méthode**. `#name4tty` peut également définir la méthode à utiliser, ce sera alors un `Symbol` :
|
509
|
+
|
510
|
+
~~~ruby
|
511
|
+
class MaClasseManaged
|
512
|
+
def name4tty
|
513
|
+
:patronyme
|
514
|
+
end
|
515
|
+
|
516
|
+
def patronyme
|
517
|
+
@patronyme ||= "#{prenom} #{nom}".strip
|
518
|
+
end
|
519
|
+
end
|
520
|
+
~~~
|
521
|
+
|
522
|
+
Noter qu’on peut aussi le faire par alias :
|
523
|
+
|
524
|
+
~~~ruby
|
525
|
+
class MaClasseManaged
|
526
|
+
alias :name4tty :patronyme
|
527
|
+
end
|
528
|
+
~~~
|
529
|
+
|
530
|
+
* surclassement. Il est possible de redéfinir la méthode (de classe) utilisée pour définir l’attribut `:name4tty` dans les options utilisées. Par exemple avec la méthode de classe `::choose` :
|
531
|
+
|
532
|
+
~~~ruby
|
533
|
+
class MaClasseManaged
|
534
|
+
def self.choose
|
535
|
+
data_manager.choose({name4tty: :patronyme})
|
536
|
+
end
|
537
|
+
end
|
538
|
+
~~~
|
539
|
+
|
540
|
+
De cette manière, chaque fois que `MaClasseManaged.choose` sera invoquée, cette méthode sera utilisée, redéfinissant la propriété à utiliser.
|
541
|
+
|
542
|
+
Il est également possible de définir une procédure plutôt qu’un symbol. Elle sera alors évaluée sur l’instance (noter, ci-dessous, également, comment on peut garder des options qui seraient passées) :
|
543
|
+
|
544
|
+
~~~ruby
|
545
|
+
class MaClasseManaged
|
546
|
+
def self.choose(options = nil)
|
547
|
+
options ||= {}
|
548
|
+
options.merge!({name4tty: Proc.new { |ins| "#{inst.prenom} #{inst.nom}".strip })
|
549
|
+
data_manager.choose(options)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
~~~
|
553
|
+
|
554
|
+
|
555
|
+
|
556
|
+
* **attribut d’option**. Enfin, il est possible, ponctuellement de définir la méthode qui doit être utilisée en renseignant les `:options` qui accompagnent souvent un affichage. Par exemple, pour la méthode de classe `::choose`, qui permet de choisir une instance, on peut utiliser :
|
557
|
+
|
558
|
+
~~~ruby
|
559
|
+
class MaClasseManaged
|
560
|
+
def self.envoyer_carte
|
561
|
+
destinataire = choose({name4tty: :patronyme})
|
562
|
+
end
|
563
|
+
end
|
564
|
+
~~~
|
565
|
+
|
566
|
+
Ici aussi on peut avoir en valeur soit un `Symbol`, pour une méthode, soit une procédure qui prendra en premier argument l’instance traitée.
|
567
|
+
|
568
|
+
Notez que ces méthodes sont présentées par ordre inverse de précédence. C’est-à-dire que c’est la dernière qui sera utilisée prioritairement et la première qui sera utilisée en dernier recours. Donc, dans l’ordre, l’application fera :
|
569
|
+
|
570
|
+
1. attribut direct dans les options envoyées (définissant soit une méthode — `Symbol`— soit une procédure — `Proc`),
|
571
|
+
2. redéfinition de la méthode générale de classe (par exemple `::choose`),
|
572
|
+
3. à égalité : méthode d’instance `#name4tty` (retournant un string, la valeur à afficher, ou bien un symbol, la méthode à utiliser)
|
573
|
+
4. la deuxième propriété définie dans les [données des propriétés][].
|
574
|
+
|
575
|
+
---
|
576
|
+
|
577
|
+
<a name="conditional-property"></a>
|
578
|
+
|
579
|
+
## Propriété conditionnelle
|
580
|
+
|
581
|
+
Cf. l'[attribut `:if`](#attribut-if)
|
582
|
+
|
583
|
+
---
|
584
|
+
|
585
|
+
<a name="formatage-affichage"></a>
|
586
|
+
|
587
|
+
## Formatage de l’affichage des données
|
588
|
+
|
589
|
+
La classe managée peut définir des méthodes de formatage d’affichage particulières pour chaque donnée. Par convention, ce nom doit être `f_<property name>`. Par exemple, si la propriété est **`name`** alors le nom de la méthode de mise en forme de sa valeur doit être par défaut **`f_name`**.
|
590
|
+
|
591
|
+
Par exemple :
|
592
|
+
|
593
|
+
~~~ruby
|
594
|
+
class MaClasseManaged
|
595
|
+
|
596
|
+
def age
|
597
|
+
@age ||= data[:age]
|
598
|
+
end
|
599
|
+
|
600
|
+
def f_age
|
601
|
+
"#{age} ans"
|
602
|
+
end
|
603
|
+
|
604
|
+
end
|
605
|
+
~~~
|
606
|
+
|
607
|
+
On peut cependant définir un nom de méthode propre en définissant la propriété **`:mformate`** dans les [données des propriétés][].
|
608
|
+
|
609
|
+
> Note : toutes les valeurs commençant par “`:m` dans la définition des propriétés concernent des méthodes.
|
610
|
+
|
611
|
+
Par exemple :
|
612
|
+
|
613
|
+
~~~ruby
|
614
|
+
class MaClasse
|
615
|
+
DATA_PROPERTIES = [
|
616
|
+
{prop: :name, name:"Patronyme", type: :string, specs:ALL_SPECS, mformate: :formate_nom}
|
617
|
+
{prop: :sexe, name:'Sexe', type: :string, specs:ALL_SPECS}
|
618
|
+
]
|
619
|
+
|
620
|
+
def name; @name ||= data[:name] end
|
621
|
+
def sexe; @sexe ||= data[:sexe] end
|
622
|
+
def formate_nom
|
623
|
+
"#{sexe == 'F' ? "Madame" : "Monsieur"} #{name}"
|
624
|
+
end
|
625
|
+
end #/class
|
626
|
+
~~~
|
627
|
+
|
628
|
+
|
629
|
+
|
630
|
+
### Transformation de la donnée en entrée ou en sortie
|
631
|
+
|
632
|
+
Quand on attend un nom (patronyme), on peut vouloir systématiquement le passer en capitale, quelle que soit l’entrée de l’utilisateur.
|
633
|
+
|
634
|
+
Pour ce faire, dans la donnée de la propriété dans `DATA_PROPERTIES`, on ajoute l’attribut `:itransform` (qui signifie “input transform” ou “transformation de la donnée entrée”) qui transforme la donnée entrée (donc la donnée qui sera enregistrée) ou l’attribut `:mformat` qui transforme la valeur enregistrée seulement à l’affichage.
|
635
|
+
|
636
|
+
Ces attributs peuvent avoir différents types de valeur :
|
637
|
+
|
638
|
+
* **une procédure** qui reçoit en premier argument l’instance et en second argument la valeur entrée
|
639
|
+
* **un symbol**. qui définit :
|
640
|
+
* soit une **méthode de la valeur** (p.e. `:upcase` pour pour un `String` ou `:round` pour un nombre)
|
641
|
+
* soit une **méthode de l’instance** — qui reçoit en premier argument la valeur.
|
642
|
+
|
643
|
+
|
644
|
+
---
|
645
|
+
|
646
|
+
<a name="data-manager"></a>
|
647
|
+
|
648
|
+
## Atteindre le manager de données (`#data_manager`)
|
649
|
+
|
650
|
+
Depuis la classe, on peut faire appel au manager de données à l’aide de `data_manager`. Par exemple :
|
651
|
+
|
652
|
+
~~~ruby
|
653
|
+
MaClasseManaged.data_manager.save_format
|
654
|
+
# Retourne :yaml si le format défini (dans @@save_format) est :yaml
|
655
|
+
~~~
|
656
|
+
|
657
|
+
La seule méthode du data manager qui est exposée publiquement d’office, c’est la propriété **`save_location`** qui retourne le chemin d’accès soit au fichier de données (si `@@save_system = :file`) soit au dossier des fiches (si `@@save_system = :card`). On peut l’atteindre par :
|
658
|
+
|
659
|
+
~~~ruby
|
660
|
+
MaClasseManaged.save_location
|
661
|
+
# => /path/to/folder/de/sauvegarde/
|
662
|
+
~~~
|
663
|
+
|
664
|
+
---
|
665
|
+
|
666
|
+
## Choisir un item
|
667
|
+
|
668
|
+
Parmi les méthodes les plus pratiques du manage de données, il y a la méthode `choose` (implémentée dans la classe) qui permet de choisir un élément parmi tous ceux existant, avec ou non un filtre :
|
669
|
+
|
670
|
+
~~~ruby
|
671
|
+
MaClasseManaged.choose(options = nil)
|
672
|
+
# => retourne l'instance choisie
|
673
|
+
~~~
|
674
|
+
|
675
|
+
Les options sont les suivantes :
|
676
|
+
|
677
|
+
~~~
|
678
|
+
:question La question à poser
|
679
|
+
:multi [Boolean] Si true, on peut choisir plusieurs items
|
680
|
+
:create [Boolean] Si true, on peut créer un nouvel item
|
681
|
+
:complete [Boolean] Si true, on ajoute un menu "Finir" pour… finir
|
682
|
+
Utile lorsque le choix est appelé en boucle quand on ne
|
683
|
+
veut pas (ou ne peut pas) utiliser :multi (ci-dessus)
|
684
|
+
:finir [Boolean] idem
|
685
|
+
:filter [Hash] Le filtre des éléments (cf. ci-dessous)
|
686
|
+
:exclude [Array<Ids>] Liste des identifiants à exclure de la liste
|
687
|
+
:default [Array<Ids>] Quand :multi est true, liste des ids qui
|
688
|
+
doivent être sélectionnés par défaut.
|
689
|
+
:sort_key Propriété qui doit servir de clé de classement
|
690
|
+
:sort_dir Direction du classement, 'asc' (défaut) ou 'desc'
|
691
|
+
~~~
|
692
|
+
|
693
|
+
### Filtre pour `choose`
|
694
|
+
|
695
|
+
La propriété `:filter` permet à la méthode `#choose` de choisir seulement un type d’élément. C’est un table avec des clés qui sont les propriétés des items.
|
696
|
+
|
697
|
+
Il y a quelques valeurs spéciales :
|
698
|
+
|
699
|
+
~~~
|
700
|
+
:periode [Periode] Définit la période dans laquelle l'item doit se trouver
|
701
|
+
Pour juger de la place de l'item, le filtre se sert d'une propriété
|
702
|
+
:date, :created_at ou :time qui doit donc exister.
|
703
|
+
~~~
|
704
|
+
|
705
|
+
### Grouper les éléments pour `choose`
|
706
|
+
|
707
|
+
On peut aussi grouper les éléments pour les choisir en deux temps. Cf. ci-après.
|
708
|
+
|
709
|
+
---
|
710
|
+
|
711
|
+
### Groupement des items (`@@group_by`)
|
712
|
+
|
713
|
+
Lorsqu'il y a beaucoup d’items, il peut être plus clair de les grouper pour voir les choisir plus facilement. Ou par exemple, ce peut être simplement une histoire de cohérence sémantique. Par exemple, si l’on veut choisir un livre, on peut vouloir dans un premier temps le choisir simplement par le titre, sans avoir pour chaque livre les versions de support (papier, numérique, …) ou les langues.
|
714
|
+
|
715
|
+
Pour ce faire, on définit la variable générale **`@@group_by`** qui déterminera que la classe sera toujours groupée par cette donnée. Elle peut se définir au même niveau que les variables `@@save_location` etc. Sa valeur est une clés définie dans `DATA_PROPERTIES`.
|
716
|
+
|
717
|
+
~~~ruby
|
718
|
+
@@group_by = :<props>
|
719
|
+
~~~
|
720
|
+
|
721
|
+
Par exemple :
|
722
|
+
|
723
|
+
~~~ruby
|
724
|
+
class MaClasseManaged
|
725
|
+
DATA_PROPERTIES = [
|
726
|
+
{prop: :id, name: "ID", ....}
|
727
|
+
{prop: :livre, name: "Livre", ...}
|
728
|
+
{prop: :name, name: "Nom", ....}
|
729
|
+
{prop: :cate, name: "Catégorie", ...}
|
730
|
+
]
|
731
|
+
|
732
|
+
@@save_location = "path/to/the/file"
|
733
|
+
|
734
|
+
@@group_by = :cate
|
735
|
+
|
736
|
+
end
|
737
|
+
~~~
|
738
|
+
|
739
|
+
Si la propriété est une propriété de type *identifiant* (par exemple `livree_id`) alors les items seront rassemblé sous l’instance correspondante et le nom de cette instance sera utilisée pour l’affichage.
|
740
|
+
|
741
|
+
Si la propriété choisie est une propriété quelconque, alors c’est sa valeur qui est utilisée dans la liste affichée. Par exemple, s’il existe les catégorie (`:cate`) “Boisson”, “Entrée” et “Fromage”, alors tous les éléments dans la catégorie “Boisson” seront rassemblés sous ce nom unique, tous les éléments de catégorie “Fromage” seront rassemblés sous ce nom, etc.
|
742
|
+
|
743
|
+
---
|
744
|
+
|
745
|
+
<a name="les-plus"></a>
|
746
|
+
|
747
|
+
|
748
|
+
## Les petits plus
|
749
|
+
|
750
|
+
Le fait de travailler avec `Clir::DataManager` offre de nombreux avantages, comme on a pu le voir. Il existe cependant quelques petites astuces à connaitre.
|
751
|
+
|
752
|
+
### Messages féminisés
|
753
|
+
|
754
|
+
Pour obtenir des messages adaptés au genre d’une classe, on peut définir la méthode de classe `::feminine?` qui reverra true dans le cas d’une classe féminine. Par exemple :
|
755
|
+
|
756
|
+
~~~ruby
|
757
|
+
class PaireDeLunettes
|
758
|
+
def self.feminine?; true end
|
759
|
+
end
|
760
|
+
|
761
|
+
# Produira par exemple le message suivant à la création d'une nouvelle
|
762
|
+
# instance : "Nouvelle PaireDeLunettes créée avec succès !"
|
763
|
+
|
764
|
+
class SacAMain
|
765
|
+
def self.feminine?; false end # <= mais inutile, car par défaut
|
766
|
+
end
|
767
|
+
|
768
|
+
# Produira par exemple le message suivant à la création d'une nouvelle
|
769
|
+
# instance : "Nouveau SacAMain créé avec succès !"
|
770
|
+
|
771
|
+
|
772
|
+
~~~
|
773
|
+
|
774
|
+
|
775
|
+
|
776
|
+
### Filtrer la liste des propriétés
|
777
|
+
|
778
|
+
Quand la liste des propriétés de l’instance est affichée, par exemple pour l’éditer (aka la modifier), on peut atteindre très rapidement la propriété à modifier en tapant ses premières lettres (ou ses lettres caractéristiques). Cela filtre la liste des propriétés et n’affiche que les propriétés correspondant au filtre. Si la liste des propriétés est longue, on peut énormément se simplifier la vie avec cette astuce.
|
779
|
+
|
780
|
+
|
781
|
+
|
782
|
+
|
783
|
+
|
784
|
+
[données des propriétés]: #data-properties
|