Collections

4

Traduction complétée à

Au programme de ce chapitre :

  • 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 !
  • 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 :

    Posts = new Mongo.Collection('posts');
    
    lib/collections/posts.js

    Commit 4-1

    Ajout d'une collection d'articles

    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.

    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. 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.

    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

    Le 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

    La console navigateur
    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

    Le Shell Meteor
    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

    Le Shell Mongo
    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.

    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 :

    meteor mongo
    
    > db.posts.insert({title: "A new post"});
    
    > db.posts.find();
    { "_id": ObjectId(".."), "title" : "A new post"};
    
    Le Shell Mongo

    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.

    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.

    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.

    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).

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    Le Shell Mongo
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    Console du premier navigateur

    Créons un nouvel article. Dans l'une des consoles de navigateur, exécutez une commande d'insertion :

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    Console du premier navigateur

    Sans surprise, l'article a été créé dans la collection local. Maintenant vérifions Mongo :

    ❯ db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    {title: "A second post", _id: 'yyy'};
    
    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 :

     Posts.find().count();
    2
    
    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 :

    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 :

    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 :

    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'
      });
    }
    
    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 :

     Posts.find().fetch();
    
    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 :

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    
    client/templates/posts/posts_list.js

    Commit 4-3

    Affichage de collection dans le template `postsList`.

    Chercher & Récupérer

    Dans Meteor, find() retourne un cursor (curseur), qui est une source de données réactive. 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).

    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) :

    Utiliser des données dynamiques
    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 :

     Posts.insert({
      title: 'Meteor Docs',
      author: 'Tom Coleman',
      url: 'http://docs.meteor.com'
    });
    
    Console navigateur

    Regardez dans le navigateur – vous devriez voir ça :

    Ajout d'articles via la console
    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.

    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.

    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 :

    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 :

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    server/publications.js

    Dans le client, nous avons besoin de souscrire à la publication. Nous allons ajouter la ligne suivante au fichier main.js :

    Meteor.subscribe('posts');
    
    client/main.js

    Commit 4-4

    Suppression de `autopublish` et mise en place d'une publi…

    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.