-
Notifications
You must be signed in to change notification settings - Fork 32
/
04-collections.md.erb
356 lines (229 loc) · 19 KB
/
04-collections.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
---
title: Collections
slug: collections
date: 0004/01/01
number: 4
points: 5
photoUrl: http://www.flickr.com/photos/73449134@N04/8270793784/
photoAuthor: Mike Lewinski
contents: Découvrir une fonctionnalité coeur de Meteor, les collections temps réel.|Comprendre comment la synchronisation des données Meteor fonctionne.|Intégrer les collections dans nos templates.|Transformer notre premier prototype en application temps réel fonctionnelle !
paragraphs: 72
---
Dans le chapitre un, nous avons parlé d'une fonctionnalité coeur de Meteor, la synchronisation automatique des données entre le client et le serveur.
Une collection est une structure de données spéciale qui prend soin de conserver vos données dans la base de donnée permanente MongoDB, côté serveur, et qui ensuite la synchronise en temps réel avec chaque navigateur connecté.
Nous voulons que nos articles soient permanents et partagés entre les utilisateurs, donc nous allons commencer par créer une collection appelée `Posts` pour les stocker dedans.
Les collections sont un élément central quelle que soit l'application, ainsi, pour s'assurer qu'elles sont toujours définies en premier on les placera dans le dossier `lib`, donc, si vous ne l'avez pas déjà fait, créez un répertoire `collections/` dans `lib`, et ensuite un fichier `posts.js` dedans. Finalement ajoutez :
~~~js
Posts = new Mongo.Collection('posts');
~~~
<%= caption "lib/collections/posts.js" %>
<%= commit "4-1", "Ajout d'une collection d'articles" %>
<% note do %>
### Var ou pas Var ?
Dans Meteor, le mot-clé `var` limite le champ d'un objet au fichier courant. Ici, nous voulons rendre la collection `Posts` disponible dans l'application entière, et c'est la raison pour laquelle nous **n'**utilisons **pas** le mot-clé var ici.
<% end %>
Les applications web disposent de trois façons basiques de stocker des données, chacune correspondant à un rôle différent :
- **La mémoire du navigateur :** les choses comme les variables JavaScript sont stockées dans la mémoire du navigateur, ce qui signifie qu'elle ne sont pas *permanentes* : elles sont locales à l'onglet courant du navigateur, et disparaîtront aussitôt que vous le fermez.
- **Le stockage du navigateur :** les navigateurs peuvent aussi stocker des données de manière permanente en utilisant des cookies ou [le stockage local](http://diveintohtml5.info/storage.html). Bien que ces données perdureront session après session, elles sont *locales* à l'utilisateur courant (mais disponibles dans tous les onglets) et ne peuvent pas être partagées facilement entre les utilisateurs.
- **La base de données côté serveur** : le meilleur endroit pour stocker des données que l'on veut aussi rendre accessibles à plus d'un utilisateur est la bonne vieille base de donnée (MongoDB étant la solution par défaut pour les applications Meteor).
Meteor utilise les trois façons, et synchronisera parfois les données d'un endroit à l'autre (comme nous le verrons bientôt). Cela dit, la base de donnée reste la source "canonique" de données : elle contient la copie originale de vos données.
### Client & Serveur
Le code contenu dans des dossiers différents de `client/` ou `/serveur` sera lancé dans les *deux* contextes. Ainsi, la collection `Posts` est disponible à la fois sur le client et sur le serveur. Cependant, ce qu'elle fait dans chaque environnement peut être bien différent.
Sur le serveur, la collection à pour objectif de communiquer avec la base de données MongoDB, et de lire et écrire chaque changement. Dans cette optique, elle peut être comparée à une librairie standard de base de donnée.
En revanche, sur le client, la collection est une copie d'un sous-ensemble de la réelle et canonique collection. La collection côté client est constamment mise à jour avec ce sous-ensemble, (la plupart du temps) de manière transparente.
<% note do %>
### Console vs Console vs Console
Dans ce chapitre, nous allons commencer à utiliser la **console du navigateur**, qu'il ne faut pas confondre avec le **terminal**, le **shell Meteor** ou le **shell Mongo**. Voici un topo rapide de chacuns d'entre eux.
#### Terminal
<%= screenshot "terminal", "Le Terminal" %>
- Appelé par votre système d'exploitation.
- **Côté serveur** `console.log()` affiche la sortie ici.
- Invité : `$`.
- Egalement connu comme : Shell, Bash.
#### Console Navigateur
<%= screenshot "browser-console", "La console navigateur" %>
- Appelé depuis le navigateur, exécute du code JavaScript.
- **Côté client** `console.log()` affiche la sortie ici.
- Invité : `❯`.
- Egalement connu comme : Console JavaScript, Console Devtools.
#### Shell Meteor
<%= screenshot "meteor-shell", "Le Shell Meteor" %>
- Appelé depuis le terminal avec `meteor shell`.
- Il vous donne un accès direct au code côté serveur de votre application.
- Invité : `>`.
#### Shell Mongo
<%= screenshot "mongo-shell", "Le Shell Mongo" %>
- Appelé depuis le terminal avec `meteor mongo`.
- Vous donne l'accès direct à la base de donnée de l'application.
- Invité : `>`.
- Egalement connu comme : Console Mongo.
Notez que dans chaque cas, vous n'êtes pas supposé écrire le caractère de l'invité (`$`, `❯`, or `>`) comme partie de la commande. Et vous pouvez deviner que les lignes qui *ne* commencent *pas* avec l'invité sont la sortie (résultat) de la commande précédente.
<% end %>
### Collections côté Serveur
Revenons sur le serveur ; la collection agit comme une API dans votre base de donnée Mongo. Dans votre code côté serveur, ça vous permet d'écrire des commandes Mongo telles que `Posts.insert()` ou `Posts.update()`, et et elles feront les changements dans la collection `posts` stockées dans Mongo.
Pour regarder dans la base de donnée Mongo, ouvrez un second terminal (`meteor` est toujours en cours d'exécution sur votre premier), et allez dans le répertoire de votre application. Ensuite, exécutez la commande `meteor mongo` pour initier un shell Mongo, dans lequel vous pouvez taper des commandes Mongo standards (et comme habituellement, vous pouvez quitter avec le raccourci clavier `ctrl+c`). Par exemple, insérons un nouvel article :
~~~bash
meteor mongo
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "Le Shell Mongo" %>
<% note do %>
### Mongo sur Meteor.com
Notez qu'en hébergeant votre application sur *.Meteor.com, vous pouvez également accéder au shell Mongo de votre application hébergée avec `meteor mongo myApp`.
Et pendant que vous y êtes, vous pouvez également retrouver les journaux (logs) de votre application en tapant `meteor logs myApp`.
<% end %>
La syntaxe de Mongo est familière, vu qu'elle utilise une interface JavaScript. Nous ne ferons pas plus de manipulation de données dans le shell Mongo, mais vous pourrez y jeter un oeil de temps en temps pour vous assurer de ce qui s'y trouve.
### Collections côté Client
Les collections sont plus intéressantes côté client. Quand vous déclarez `Posts = new Mongo.Collection('posts');` sur le client, ce que vous êtes en train de créer est un *cache local dans le navigateur* de la collection Mongo réelle. Quand nous parlons d'une collection côté client en tant que "cache", nous voulons dire qu'elle contient le *sous-ensemble* de vos données, et offre un accès *rapide* à ces données.
Il est important de comprendre ces points qui sont fondamentaux dans la façon dont Meteor fonctionne. En général, une collection côté client consiste en un sous-ensemble de tous vos documents stockés dans la collection Mongo (après tout, en général, nous ne voulons pas envoyer toute la base de données au client).
Deuxièmement, ces documents sont stockés *dans la mémoire du navigateur*, ce qui signifie qu'y accéder est tout simplement instantané. Donc il n'y a pas de longs trajets vers le serveur ou la base de données pour récupérer les données quand vous appelez `Posts.find()` sur le client, vu que les données sont déjà pré-chargées.
<% note do %>
### Présentation de MiniMongo
L'implémentation de Mongo côté client s'appelle MiniMongo. C'est n'est pas encore une implémentation parfaite, et vous pourrez rencontrer quelques fonctionnalités occasionel de Mongo qui ne fonctionneront pas dans MiniMongo. Cependant, toutes les fonctionnalités qui sont dans ce livre fonctionne de façon similaire dans Mongo et MiniMongo.
<% end %>
### Communication Client-Serveur
La clé de voûte de tout ça est comment la collection côté client synchonise ses données avec la collection côté serveur du même nom (`'posts'` dans notre cas).
Plutôt que d'expliquer ça en détail, regardons juste ce qu'il se passe.
Commençons par ouvrir 2 fenêtres de navigateur, et accéder à la console du navigateur dans l'une des deux. Ensuite, ouvrez le shell Mongo dans le terminal.
À ce moment, vous devriez pouvoir trouver le document créé précédemment dans les trois contextes (notez que *l'interface utilisateur* de notre application affiche toujours nos trois précédents posts factices. Ignorez-les pour le moment).
~~~bash
> db.posts.find();
{title: "A new post", _id: ObjectId("..")};
~~~
<%= caption "Le Shell Mongo" %>
~~~js
❯ Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};
~~~
<%= caption "Console du premier navigateur" %>
Créons un nouvel article. Dans l'une des consoles de navigateur, exécutez une commande d'insertion :
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "Console du premier navigateur" %>
Sans surprise, l'article a été créé dans la collection local. Maintenant vérifions Mongo :
~~~bash
❯ db.posts.find();
{title: "A new post", _id: ObjectId("..")};
{title: "A second post", _id: 'yyy'};
~~~
<%= caption "Le Shell Mongo" %>
Comme vous pouvez le voir, l'article a été créé dans la base de données Mongo, sans qu'on ait besoin d'écrire une seule ligne de code d'envoi au serveur (bien, strictement parlant, nous avons écrit une _seule_ ligne de code : `new Mongo.Collection('posts')`). Mais ce n'est pas tout !
Ouvrez la fenêtre du second navigateur et entrez ceci dans la console :
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Console du second navigateur" %>
L'article est ici aussi ! Quand bien même nous n'avons jamais rafraîchit ou même interagit avec le second navigateur, et nous avons certainement pas écrit de code pour envoyer les mises à jour. C'est arrivé par magie -- et instantanément aussi, bien que ça devienne plus évident plus tard.
Ce qui est arrivé est que la collection côté serveur a été informé par la collection côté client d'un nouvel article, et a pris en charge la tâche d'insérer l'article dans la base de données Mongo et de renvoyer l'information à toutes les autres collections `post` connectées.
Récupérer les articles dans la console du navigateur n'est pas très utile. Nous apprendrons bientôt comment lier les données dans nos templates, et par conséquent modifier notre simple prototype HTML en application web temps réel fonctionnelle.
### Remplir la base de données
Regarder le contenu de nos collections sur le navigateur est une chose, mais ce que nous aimerions vraiment faire c'est afficher les données, et les changements de ces données, sur l'écran. En faisant ça nous transformerons notre simple *page* web affichant du contenu statique, en *application* web temps réel avec des données changeantes, dynamiques.
La première chose que nous allons faire est de mettre des données dans notre base de données. Nous allons faire ça avec un fichier d'installation (fixture) qui charge un ensemble de données structurées dans la collection `Posts` quand le serveur démarre pour la première fois.
Premièrement, assurons nous qu'il n'y a rien dans la base de données. Nous utiliserons `meteor reset`, qui vide votre base de données et remet à zéro votre projet. Bien sûr, vous serez vraiment vigilant avec cette commande une fois que vous aurez commencé à travailler sur des projets réels.
Arrêtez le serveur Meteor (en faisant `ctrl-c`) et ensuite, dans le terminal, exécutez :
~~~bash
meteor reset
~~~
La commande reset vide la base de données. C'est une commande utile en développement, où il y a de fortes probabilités que votre base de données soit dans des états inconsistants.
Relançons notre application Meteor :
~~~bash
meteor
~~~
Maintenant que la base de données est vide, vous pouvez ajouter le code suivant qui va charger trois articles quand le serveur va démarrer si la collection `Posts` est vide :
~~~js
if (Posts.find().count() === 0) {
Posts.insert({
title: 'Introducing Telescope',
url: 'http://sachagreif.com/introducing-telescope/'
});
Posts.insert({
title: 'Meteor',
url: 'http://meteor.com'
});
Posts.insert({
title: 'The Meteor Book',
url: 'http://themeteorbook.com'
});
}
~~~
<%= caption "server/fixtures.js" %>
<%= commit "4-2", "Ajout de données dans la collection posts." %>
Nous avons placé ce fichier dans le répertoire `server/`, il ne sera donc jamais chargé dans le navigateur d'un utilisateur. Le code sera exécuté immédiatement quand le serveur démarrera, et fera des appels d'insertion (`insert`) sur la base de données pour ajouter les trois articles dans notre collection `Posts`.
Maintenant relancez votre serveur avec `meteor`, et ces trois articles seront chargés dans la base de données.
### Des données dynamiques
Si nous ouvrons une console navigateur, nous verrons les trois articles chargés dans MiniMongo :
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Console navigateur" %>
Pour afficher ces trois articles dans le rendu HTML, nous utiliserons un template helper.
Dans le chapitre 3 nous avons vu comment Meteor nous permettait de joindre un *data context* (contexte de données) à nos templates Spacebars pour construire des vues HTML de simple structures de données. Nous pouvons joindre nos données de collections de la même façon. Nous remplacerons simplement notre objet JavaScript statique `postsData` par une collection dynamique.
En parlant de ça, vous êtes libres de supprimer le code `postsData` à partir d'ici. Voici à quoi devrait ressembler `posts_list.js` maintenant :
~~~js
Template.postsList.helpers({
posts: function() {
return Posts.find();
}
});
~~~
<%= caption "client/templates/posts/posts_list.js" %>
<%= highlight "2~4" %>
<%= commit "4-3", "Affichage de collection dans le template `postsList`." %>
<% note do %>
### Chercher & Récupérer
Dans Meteor, `find()` retourne un *cursor* (curseur), qui est une [source de données réactive](http://docs.meteor.com/#find). Quand nous voulons récupérer son contenu, nous pouvons utiliser `fetch()` sur ce curseur pour le transformer en tableau.
A l'intérieur d'une application, Meteor est assez intelligent pour savoir comment itérer sur un curseur sans avoir à les convertir d'abord explicitement en tableaux. C'est pourquoi vous ne verrez pas `fetch()` si souvent que ça dans le code Meteor actuel (et pourquoi nous ne l'utiliserons pas dans l'exemple ci-dessous).
<% end %>
Plutôt que de prendre une liste d'articles sous forme de tableau statique depuis une variable, nous retournons maintenant un curseur de notre helper `posts` (bien que les choses n'apparaîtront pas vraiment différemment puisque nous utilisons toujours les mêmes données) :
<%= screenshot "4-3", "Utiliser des données dynamiques" %>
Notre helper `{{#each}}` a itéré sur tous nos `Posts`, et les a affiché à l'écran. La collection côté serveur a récupéré les articles de Mongo, les a envoyés vers la collection côté client, et nos helpers Spacebars les ont affichés dans notre template.
Maintenant, nous pouvons aller un peu plus loin; ajoutons un autre article via la console :
~~~js
❯ Posts.insert({
title: 'Meteor Docs',
author: 'Tom Coleman',
url: 'http://docs.meteor.com'
});
~~~
<%= caption "Console navigateur" %>
Regardez dans le navigateur -- vous devriez voir ça :
<%= screenshot "4-4", "Ajout d'articles via la console" %>
Vous venez juste de voir la réactivité en action pour la première fois. Quand nous parlions de Spacebars qui itérait sur le curseur `Posts.find()`, il savait comment observer les changements sur le curseur, et a corrigé le HTML de la plus simple façon pour afficher les données correctes à l'écran.
<% note do %>
### Inspecter les changements du DOM
Dans ce cas, le plus simple changement possible est d'ajouter un autre `<div class="post">...</div>`. Si vous voulez vous assurer du changement, ouvrez l'inspecteur du DOM et sélectionnez le `<div>` correspondant à un des articles existants.
Maintenant, dans la console JavaScript, insérez un autre article. Quand vous revenez sur l'inspecteur, vous allez voir un `<div>` supplémentaire, correspondant au nouvel article, mais vous aurez encore le même `<div>` existant sélectionné. C'est un moyen utile de voir quand les éléments ont été re-rendus et quand ils ont été laissés seuls.
<% end %>
### Connecter les collections : Publications et Souscriptions
Depuis le début, nous avons le paquet `autopublish` activé, qui n'est pas conseillé pour les applications en production. Comme son nom l'indique, ce paquet dit simplement que chaque collection doit être partagée dans sa totalité à chaque client connecté. Ce n'est pas ce que nous voulons vraiment, donc désactivons le.
Ouvrez un nouveau terminal, et tapez :
~~~bash
meteor remove autopublish
~~~
L'effet est instantané. Si vous regardez dans votre navigateur maintenant, vous verrez que tous vos posts ont disparus ! C'est parce que nous nous reposions sur `autopublish` pour s'assurer que la collection posts côté client était un mirroir de tous les articles dans la base de donnée.
Eventuellement nous allons avoir besoin de nous assurer que nous transférons seulement les articles que l'utilisateur a besoin de voir (en prenant en compte les choses comme la pagination). Mais pour l'instant, nous allons juste configurer `Posts` pour qu'il soit publié dans sa totalité.
Pour faire cela, nous créons une fonction `publish()` qui retourne un curseur référençant tous les posts :
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
~~~
<%= caption "server/publications.js" %>
Dans le client, nous avons besoin de *souscrire* à la publication. Nous allons ajouter la ligne suivante au fichier `main.js` :
~~~js
Meteor.subscribe('posts');
~~~
<%= caption "client/main.js" %>
<%= commit "4-4", "Suppression de `autopublish` et mise en place d'une publication basique." %>
Si nous vérifions le navigateur, nos articles sont de retour. Pfiou !
### Conclusion
Donc qu'est-ce qu'on a fait ? Bien que nous n'avons pas encore d'interface utilisateur, ce que nous avons maintenant est une application web fonctionnel. Nous pourrions déployer cette application sur Internet, et (en utilisant la console du navigateur) commencer à poster des nouvelles histoires et les voir apparaître dans les navigateurs d'utilisateurs partout à travers le monde.