-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy path04-collections.md.erb
334 lines (215 loc) · 17.5 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
---
title: Collezioni
slug: collections
date: 0004/01/01
number: 4
contents: 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!
paragraphs: 72
---
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:
~~~js
Posts = new Meteor.Collection('posts');
~~~
<%= caption "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.
<% note do %>
### 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.
<% end %>
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.
<% note do %>
### 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
<%= screenshot "terminal", "Il Terminale" %>
- Eseguito dal tuo sistema operativo.
- `console.log()` eseguito **lato server** produce output qui.
- Prompt: `$`.
- Conosciuto anche come: Shell, Bash.
#### Console del Browser
<%= screenshot "browser-console", "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
<%= screenshot "mongo-shell", "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.
<% end %>
### 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:
~~~bash
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "La Shell di Mongo" %>
<% note do %>
### 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`.
<% end %>
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.
<% note do %>
### 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.
<% end %>
### 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.
~~~bash
> db.posts.find();
{title: "A new post", _id: ObjectId("..")};
~~~
<%= caption "La Shell di Mongo" %>
~~~js
❯ Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};
~~~
<%= caption "La console del primo browser" %>
Creiamo un nuovo post. In una delle finestre del browser eseguiamo un inserimento:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "La console del primo browser" %>
Come ci si poteva aspettare, il post è stato inserito nella collezione locale. Ora controlliamo Mongo:
~~~bash
❯ db.posts.find();
{title: "A new post", _id: ObjectId("..")};
{title: "A second post", _id: 'yyy'};
~~~
<%= caption "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:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "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:
~~~bash
$ 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:
~~~js
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'
});
}
~~~
<%= caption "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:
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "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`:
~~~js
Template.postsList.helpers({
posts: function() {
return Posts.find();
}
});
~~~
<%= caption "client/views/posts/posts_list.js" %>
<%= highlight "2~4" %>
<%= commit "4-3", "Wired collection into `postsList` template." %>
<% note do %>
### Find & Fetch
In Meteor, `find()` restituisce un *cursore* che rappresenta una [sorgente di dati reattiva](http://docs.meteor.com/#find). 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).
<% end %>
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:
<%= screenshot "4-3", "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:
~~~js
❯ Posts.insert({
title: 'Meteor Docs',
author: 'Tom Coleman',
url: 'http://docs.meteor.com'
});
~~~
<%= caption "La console del Browser" %>
Guardiamo di nuovo il browser -- dovremmo vedere quanto segue:
<%= screenshot "4-4", "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.
<% note do %>
### 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.
<% end %>
### 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:
~~~bash
$ 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:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
~~~
<%= caption "server/publications.js" %>
Nel client, dobbiamo sottoscrivere la pubblicazione con *subscribe*. Aggiungiamo semplicemente la riga seguente al file `main.js`:
~~~js
Meteor.subscribe('posts');
~~~
<%= caption "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.