Collezioni

4

Percentuale Tradotta

In questo capitolo imparerai:

  • Principali funzionalità del nucleo di Meteor, collezioni in tempo reale.
  • Comprendere come funziona la sincronizzazione dei dati in Meteor.
  • Integrare collezioni con i nostri template.
  • Trasformare il nostro prototipo in un'applicazione realtime funzionante!
  • Nel capitolo uno, abbiamo parlato della caratteristica principale di Meteor: la sincronizzazione automatica dei dati tra client e server.

    In questo capitolo, analizzeremo più nel dettaglio questo meccanismo, osservando il funzionamento della tecnologia chiave che lo permette: la collezione (collection) di Meteor.

    Stiamo costruendo un'applicazione sociale di notizie, e la prima cosa che vogliamo fare è creare una lista di link che le persone hanno inserito. Chiameremo “post” ognuno di questi oggetti.

    Naturalmente, dovremo salvare questi posts da qualche parte. Meteor installa anche Mongo che viene eseguito sul vostro server e funge da base di dati persistente.

    Dunque, anche se il browser dell'utente può contenere alcune informazioni di stato (ad esempio la pagina corrente, o il commento che sta scrivendo in quel momento), il server, e più precisamente Mongo, contiene la sorgente di dati permanente canonica. Canonica significa che è la stessa per tutti gli utenti: ogni utente può essere su di una pagina diversa, ma la lista originale dei post è la stessa per tutti.

    Questi dati sono salvati in Meteor all'interno di una Collezione. Una collezione è una struttura dati speciale che, per mezzo di pubblicazioni e sottoscrizioni, si prende cura della sincronizzazione in tempo reale dei dati tra il database Mongo e ogni browser degli utenti connessi, in entrambe le direzioni. Vediamo come.

    Vogliamo che i post siano permanenti e condivisi tra gli utenti, inizieremo quindi creando una collezione chiamata Posts dentro cui salvarli. Se non lo avete ancora fatto, create la cartella collections/ dentro la cartella principale della vostra applicazione, e dentro essa il file posts.js. Quindi aggiungete:

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

    Commit 4-1

    Added a posts collection

    Il codice che non si trova all'interno delle cartelle client/ e server/ verrà eseguito in entrambi i contesti. Per questo motivo la collezione Posts risulterà disponibile sia lato client che lato server. Comunque, il ruolo della collezione in ognuno dei due ambienti è molto diverso.

    Usare Var O Non Usare Var?

    In Meteor, la parola chiave var limita la visibilità di un oggetto al file corrente. Vogliamo rendere la collezione Posts visibile a tutta l'applicazione, motivo per cui la stiamo omettendo dalla sua dichiarazione.

    Sul server la collezione ha il compito di parlare con il database Mongo, leggendo e scrivendo qualsiasi cambiamento. Da questo punto di vista, può essere paragonata ad una libreria standard per database. Sul client, diversamente, la collezione è una copia sicura di un sottoinsieme della reale collezione canonica. La collezione lato client è mantenuta costantemente aggiornata, in modo quasi del tutto trasparente, con quel sottoinsieme di dati in tempo reale.

    Console vs Console vs Console

    In questo capitolo inizieremo a fare uso della console del browser, che non va confusa con il terminale o la shell di Mongo. Di seguito proponiamo una breve guida per ognuna di esse.

    Terminale

    Il Terminale
    Il Terminale
    • Eseguito dal tuo sistema operativo.
    • console.log() eseguito lato server produce output qui.
    • Prompt: $.
    • Conosciuto anche come: Shell, Bash.

    Console del Browser

    La Console del Browser
    La Console del Browser
    • Richiamata dall'interno del browser, esegue codice JavaScript.
    • console.log() eseguito lato client produce output qui.
    • Prompt: .
    • Conosciuta anche come: Console JavaScript, Console dei DevTools

    Shell di Mongo

    La Shell di Mongo
    La Shell di Mongo
    • Lanciata dal Terminale con meteor mongo o mrt mongo.
    • Permette l'accesso diretto al database dell'applicazione.
    • Prompt: >.
    • Conosciuta anche come: Console di Mongo.

    Notiamo che, per tutte, non è necessario digitare il carattere del prompt ($, , o >) come parte dei comandi. Possiamo anche assumere che ogni riga che non inizi con il prompt sia il risultato del comando precedente.

    Collezioni Lato Server

    Sul server, la collezione funge da API per il database Mongo. Questo permette, per il codice lato server, di scrivere comandi Mongo come Posts.insert() oppure Posts.update(): questi modificheranno la collezione posts in Mongo.

    Per guardare all'interno del database Mongo, aprite una seconda finestra del terminale (mentre meteor è ancora in esecuzione nella prima) e spostatevi nella cartella della vostra app. Eseguite quindi il comando meteor mongo per lanciare la shell di Mongo: al suo interno potete digitare i comandi standard di Mongo (e come usuale, potete terminarla con la combinazione di tasti ctrl+c). Per fare un esempio, inseriamo un nuovo post:

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

    Mongo su Meteor.com

    Se ospitate la vostra applicazione su *.meteor.com, potete comunque accedere alla shell di mongo dell'applicazione funzionante con meteor mongo myApp.

    E, tanto che ci siamo, potete anche ottenere i log dell'applicazione scrivendo meteor logs myApp.

    La sintassi di Mongo è familiare, visto che utilizza un'interfaccia JavaScript. Non faremo altre manipolazioni di dati dalla shell di Mongo, ma potremo ritornarci di tanto in tanto giusto per assicurarci che i dati siano presenti.

    Collezioni Lato Client

    Le collezioni sono ancora più interessanti viste dal lato client. Quando dichiariamo Posts = new Meteor.Collection('posts'); sul client, stiamo creando una copia locale nella cache del browser della reale collezione in Mongo. Quando diciamo che una collezione lato client è una “cache”, vogliamo dire che contiene un sottoinsieme dei dati ai quali offre un accesso molto rapido.

    È importante capire bene questo concetto perché risulta fondamentale al funzionamento di Meteor. In generale, una collezione lato client consiste in un sottoinsieme di tutti i documenti salvati all'interno della collezione Mongo (dopo tutto, generalmente non vogliamo mandare tutto il database al client).

    Secondariamente, questi documenti sono salvati nella memoria del browser, il che significa che accedervi è praticamente istantaneo. Quando eseguiamo Posts.find() sul client per caricare i dati, non ci sono quindi comunicazioni lente con il server o il database, perché i dati sono già pre-caricati.

    Introduzione a MiniMongo

    L'implementazione Meteor di Mongo per il lato client viene chiamata MiniMongo. Non è ancora un'implementazione perfetta e, occasionalmente, potrete scoprire che alcune funzionalità di Mongo non funzionano in MiniMongo. Nonostante ciò, tutte le funzionalità che copriremo con questo libro funzionano allo stesso modo sia in Mongo che MiniMongo.

    Comunicazione Client-Server

    La chiave di volta di tutto ciò sta nel meccanismo che la collezione lato client utilizza per sincronizzare i suoi dati con la collezione lato server che ha lo stesso nome ( in questo caso 'posts').

    Anziché spiegarlo nei dettagli, guardiamo semplicemente cosa succede.

    Iniziamo aprendo due finestre del browser e accediamo alla console del browser in entrambe. Fatto questo, apriamo una shell di Mongo dalla riga di comando. A questo punto dovremmo vedere in tutti e tre i contesti il solo documento che abbiamo creato in precedenza.

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    La Shell di Mongo
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    La console del primo browser

    Creiamo un nuovo post. In una delle finestre del browser eseguiamo un inserimento:

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    La console del primo browser

    Come ci si poteva aspettare, il post è stato inserito nella collezione locale. Ora controlliamo Mongo:

    ❯ db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    {title: "A second post", _id: 'yyy'};
    
    La Shell di Mongo

    Come possiamo vedere, il post ha percorso tutta la strada all'indietro fino al database Mongo, senza che sia stato necessario scrivere una singola linea di codice per agganciare il nostro client al server (beh, a dire il vero, una singola riga di codice l'abbiamo scritta: new Meteor.Collection('posts')). Ma questo non è tutto!

    Passiamo alla seconda finestra del browser e digitiamo quanto segue nella console del browser:

     Posts.find().count();
    2
    
    La console del secondo browser

    Il post è arrivato anche qua! Anche se non abbiamo mai ricaricato e nemmeno interagito con il secondo browser, e nemmeno abbiamo mai scritto codice per mandargli gli aggiornamenti. È successo tutto magicamente – e anche istantaneamente, anche se tutto ciò diventerà più ovvio nel seguito.

    Quel che è successo è che la collezione lato server è stata informata da una collezione lato client dell'inserimento di un nuovo post, e si è quindi presa in carico la distribuzione di questo post sia all'interno del database Mongo che all'indietro verso tutte le altre collezioni post connesse.

    Recuperare i post dalla console del browser non è così utile. Impareremo come scrivere questi dati dentro ai template, e allo stesso tempo trasformeremo il nostro semplice prototipo HTML in una applicazione web real-time funzionante.

    Rendere tutto Real-time

    Una cosa è guardare il contenuto delle Collezioni dalla console del browser, ma quello che ci piacerebbe fare veramente sarebbe visualizzare i dati, e le loro modifiche, sullo schermo. Nel fare questo trasformeremo la nostra app da una semplice pagina web che mostra contenuti statici, in una applicazione web real-time dai contenuti dinamici.

    Scopriamo come.

    Popolare il Database

    Per prima cosa inseriremo un po’ di dati all'interno del database. E lo faremo con un file di inizializzazione che carica un insieme di dati strutturati dentro la collezione Posts al primo avvio del server.

    Prima di tutto assicuriamoci che dentro al database non ci sia nulla. Useremo meteor reset, che cancella il database e resetta il progetto. Chiaramente bisogna stare molto attenti ad utilizzare questo comando, soprattutto quando si inizia a lavorare su progetti veri.

    Fermiamo il server Meteor (premendo ctrl-c) e dopo, da riga di comando, eseguiamo:

    $ meteor reset
    

    Il comando reset svuota completamente il database Mongo. Durante la fase di sviluppo può risultare molto utile, visto che ci sono buone probabilità che il database cada in uno stato inconsistente.

    Ora che il database è vuoto, possiamo aggiungere il seguente codice che carica tre post ogni volta che il server viene lanciato e la collezione Posts risulti vuota:

    if (Posts.find().count() === 0) {
      Posts.insert({
        title: 'Introducing Telescope',
        author: 'Sacha Greif',
        url: 'http://sachagreif.com/introducing-telescope/'
      });
    
      Posts.insert({
        title: 'Meteor',
        author: 'Tom Coleman',
        url: 'http://meteor.com'
      });
    
      Posts.insert({
        title: 'The Meteor Book',
        author: 'Tom Coleman',
        url: 'http://themeteorbook.com'
      });
    }
    
    server/fixtures.js

    Commit 4-2

    Added data to the posts collection.

    Abbiamo posizionato il file dentro alla cartella server/, in modo che non venga mai caricato in nessun browser utente. Il codice verrà eseguito immediatamente all'avvio del server, eseguendo delle chiamate insert sul database per aggiungere tre semplici post all'interno della collezione Posts. Visto che finora non abbiamo definito nessuna politica di sicurezza dei dati, non c'è nessuna differenza nel fare questo all'interno di un file eseguito sul server o da un browser.

    Ora lanciamo nuovamente il server con meteor: i tre post verranno inseriti nel database.

    Collegare i dati all'HTML tramite helpers

    Se ora apriamo una console del browser, vedremo tutti e tre i post caricati dentro a MiniMongo:

     Posts.find().fetch();
    
    Console del Browser

    Per vedere questi post renderizzati in HTML, possiamo utilizzare un template helper. Nel Capitolo 3 abbiamo visto come Meteor permetta di collegare un data context ai template di Spacebars per costruire viste HTML di semplici strutture dati. Possiamo anche collegare i dati delle collezioni esattamente nello stesso modo. Rimpiazzeremo semplicemente l'oggetto statico postsData di JavaScript con una collezione dinamica.

    A proposito, a questo punto sentitevi liberi di cancellare il codice postsData. Questo è come ora dovrebbe diventare posts_list.js:

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

    Commit 4-3

    Wired collection into `postsList` template.

    Find & Fetch

    In Meteor, find() restituisce un cursore che rappresenta una sorgente di dati reattiva. Quando vogliamo farne il log del contenuto, possiamo utilizzare fetch() sul cursore per trasformarlo in un vettore.

    All'interno di un applicazione, Meteor è abbastanza intelligente da sapere come iterare sui cursori senza che si debba prima convertirli in vettori. Questo è il motivo per cui non vedremo fetch() molto spesso all'interno di codice Meteor (e motivo per cui non l'abbiamo usato nell'esempio sopra).

    Ora, anziché caricare una lista di post come un vettore statico, restituiamo un cursore al nostro helper posts. Questo cosa comporta? Se torniamo al browser vediamo:

    Utilizzare dati dinamici
    Utilizzare dati dinamici

    Possiamo quindi vedere chiaramente che il nostro helper {{#each}} ha iterato su tutti i Posts e li ha visualizzati sullo schermo. La collezione lato server server ha caricato i post da Mongo, li ha passati attraverso la connessione alla collezione lato client e l'helper di Spacebars li ha passati al template.

    Ora facciamo un passo in più; aggiungiamo un altro post da console:

     Posts.insert({
      title: 'Meteor Docs',
      author: 'Tom Coleman',
      url: 'http://docs.meteor.com'
    });
    
    La console del Browser

    Guardiamo di nuovo il browser – dovremmo vedere quanto segue:

    Adding posts via the console
    Adding posts via the console

    Per la prima volta abbiamo appena visto la reattività in azione. Quando diciamo ad Spacebars di iterare sul cursore Posts.find(), sa anche come osservare quel cursore per accorgersi di cambiamenti di modo da poter modificare l'HTML nel modo più semplice possibile per visualizzare i dati corretti sullo schermo.

    Ispezionare i cambiamenti del DOM

    In questo caso, la modifica più semplice era aggiungere un altro <div class="post">...</div>. Se vogliamo verificare che questo è effettivamente ciò che è successo, apriamo il DOM inspector e selezioniamo il <div> corrispondente ad uno dei post esistenti.

    Dopodiché, nella console JavaScript, inseriamo un altro post. Tornando al DOM inspector, vedremo un <div> in più, corrispondente al nuovo post, ma lo stesso <div> del post esistente sarà ancora selezionato. Questo è di solito un modo efficace per capire se alcuni elementi sono stati ridisegnati oppure se sono rimasti invariati.

    Collegare le Collezioni: Pubblicazioni e Sottoscrizioni

    Fino a questo punto abbiamo avuto il pacchetto autopublish abilitato, il che non è previsto per applicazioni in fase di produzione. Come indica il nome stesso, questo pacchetto fa sì che ogni collezione sia interamente condivisa con ogni client connesso. Visto che questo non è ciò che vogliamo, disabilitiamolo.

    Apriamo una nuova finestra del terminale e scriviamo:

    $ meteor remove autopublish
    

    Questo ha un effetto istantaneo. Se ora guardiamo il browser, vedremo che tutti i post sono scomparsi! Questo perché stavamo sfruttando autopublish per assicurarci che la collezione lato client dei post fosse una copia esatta di tutti i post presenti nel database.

    Alla fine dello sviluppo dovremo fare in modo che vengano trasferiti solo i post che l'utente deve vedere (anche considerando cose come la paginazione). Ma per ora faremo in modo che la collezione Posts venga interamente pubblicata.

    Per farlo creiamo una semplice funzione publish() che restituisce un cursore su tutti i post:

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

    Nel client, dobbiamo sottoscrivere la pubblicazione con subscribe. Aggiungiamo semplicemente la riga seguente al file main.js:

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

    Commit 4-4

    Removed `autopublish` and set up a basic publication.

    Se controlliamo nuovamente il browser, i post sono ricomparsi. Menomale!

    Conclusione

    Quindi, cosa abbiamo ottenuto? Beh, anche se non abbiamo ancora un'interfaccia utente, quello che abbiamo al momento è un'applicazione web funzionante. Potremmo fare il deploy di questa app su internet e (utilizzando la console del browser) iniziare ad inserire notizie per vederle apparire nei browser degli altri utenti di tutto il mondo.