-
Notifications
You must be signed in to change notification settings - Fork 30
/
04-collections.md.erb
360 lines (231 loc) · 18.2 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
357
358
359
360
---
title: Colecciones
slug: collections
date: 0004/01/01
number: 4
points: 5
photoUrl: http://www.flickr.com/photos/73449134@N04/8270793784/
photoAuthor: Mike Lewinski
contents: Conoceremos la característica central de Meteor, las colecciones.|Comprenderemos cómo funciona la sincronización de datos en Meteor.|Integraremos las colecciones con las plantillas.|Convertiremos nuestro prototipo en una aplicación en tiempo real completamente funcional!.
paragraphs: 72
---
En el Capítulo uno hablamos sobre la característica central de Meteor: la sincronización automática de datos entre cliente y servidor.
En este capítulo miraremos más de cerca todo esto y observaremos cómo funciona la tecnología que lo hace posible, las **Colecciones** Meteor.
Una colección es una estructura de datos especial que se encarga de almacenar los datos de forma permanente, en una base de datos MongoDB en el servidor, y de la sincronización de datos en tiempo real con el navegador de cada usuario conectado.
Queremos que nuestros posts sean permanentes y los podamos compartir con otros usuarios, así que vamos a empezar creando una colección llamada `Posts` para poder almacenarlos.
Las colecciones son el eje central de cualquier aplicación, así que para asegurarnos de que se definen primero, las pondremos en el directorio `lib`. Si todavía no lo has hecho, crea un directorio llamado `collections/` dentro de `lib`, crea un archivo llamado `posts.js` y añade lo siguiente:
~~~js
Posts = new Mongo.Collection('posts');
~~~
<%= caption "lib/collections/posts.js" %>
<%= commit "4-1", "Colección Posts" %>
<% note do %>
### Con var o sin var
En Meteor, la palabra clave `var` limita el alcance del objeto al archivo actual. Nosotros queremos que los Posts estén disponibles para toda nuestra aplicación, por eso *no* usamos `var`.
<% end %>
### Almacenando datos
Las aplicaciones web tienen a su disposición básicamente tres formas de almacenar datos, cada una desempeñando un rol diferente:
- **La memoria del navegador:** cosas como las variables JavaScript son almacenadas en la memoria del navegador, lo que implica que no son *permanentes*: son datos locales a la pestaña del navegador y desaparecerán tan pronto como la cierres.
- **El almacén del navegador:** los navegadores también pueden almacenar datos de forma permanente usando cookies o [Local Storage](http://diveintohtml5.info/storage.html). Aunque estos datos sean permanentes de una sesión a otra, son *locales* al usuario actual (pero disponible entre las pestañas) y no se puede compartir de forma sencilla con otros usuarios.
- **La base de datos del servidor:** el mejor lugar para almacenar datos de forma permanente para que puedan estar disponibles para más de un usuario es una base de datos (Siendo MongoDB la solución por defecto para las aplicaciones Meteor).
Meteor hace uso de estas tres formas, y a veces sincroniza los datos de un lugar a otro (como veremos pronto). Dicho esto, la base de datos permanece como la fuente de datos “canónica“ que contiene la copia maestra de los datos.
### Cliente y Servidor
El código dentro de las carpetas que no sean `client/` ni `server/` se ejecutará en *ambos* contextos. Por lo que la colección `Posts` estará disponible en el lado cliente y servidor. Sin embargo, lo que hace la colección en cada entorno es bastante diferente.
En el servidor, la colección tiene la tarea de hablar con la base de datos MongoDB y leer y escribir cualquier cambio. En este sentido, se puede comparar con una librería de base de datos estándar.
En el cliente sin embargo, la colección es una copia de un *subconjunto* de la colección canónica. La colección del lado del cliente se mantiene actualizada, de forma constante y (normalmente) trasparente con ese subconjunto de datos en tiempo real.
<% note do %>
### Consola vs. Consola vs. Consola
En este capítulo hemos empezado a usar la **consola del navegador**, que no debemos confundir con la **terminal**, con la **Shell de Meteor** o con la **Shell de Mongo**. A continuación, explicamos brevemente cada una de ellas.
#### Terminal
<%= screenshot "terminal", "La Terminal" %>
- Se abre desde el sistema operativo.
- Las llamadas a `console.log()` en el **lado del servidor** se muestran por aquí.
- Prompt: `$`.
- También se conoce como: Shell, Bash
#### Consola del navegador
<%= screenshot "browser-console", "La consola del navegador" %>
- Se abre desde dentro del navegador, ejecuta código JavaScript.
- Las llamadas a `console.log()` en el **lado del cliente** se muestran por aquí.
- Prompt: `❯`.
- También se conoce como: JavaScript Console, DevTools Console
#### La consola de Meteor
<%= screenshot "meteor-shell", "La consola de Meteor" %>
- Se abre desde la Terminal con `meteor shell`.
- Te da acceso directo al código de la parte del servidor de tu aplicación.
- Prompt: `>`.
#### La consola de Mongo
<%= screenshot "mongo-shell", "La consola de Mongo" %>
- Se abre desde la Terminal con `meteor mongo`.
- Te da acceso directo a la base de datos de tu aplicación.
- Prompt: `>`.
- También se conoce como: Mongo Console
Ten en cuenta que no hay que escribir el carácter prompt (`$`, `❯`, or `>`) como parte de un comando. Y que puedes asumir como *salida*, todo lo que *no* empiece con el prompt.
<% end %>
### Colecciones en el lado del servidor
Volviendo al servidor, la colección actúa como una API de nuestra base de datos Mongo. En el código del lado del servidor, esto nos permite escribir comandos Mongo como `Posts.insert()` o `Posts.update()`, que harán cambios en la colección `posts` almacenada dentro de Mongo.
Para mirar el interior de la base de datos Mongo, abrimos una segunda ventana de terminal (mientras Meteor se está ejecutando en la primera), vamos al directorio de la aplicación y ejecutamos el comando `meteor mongo` para iniciar una shell de Mongo, en la que podemos escribir los comandos estándares de Mongo (y como de costumbre, salir con `ctrl+c`). Por ejemplo, vamos a insertar un nuevo post:
~~~bash
meteor mongo
> db.posts.insert({title: "A new post"});
> db.posts.find();
{ "_id": ObjectId(".."), "title" : "A new post"};
~~~
<%= caption "Consola de mongo" %>
<% note do %>
### Mongo en Meteor.com
Debemos saber que cuando alojamos nuestra aplicación en *.meteor.com, también podemos acceder a la consola de Mongo usando `meteor mongo myApp`.
Y ya que estamos, también podemos obtener los logs de nuestra aplicación escribiendo `meteor logs myApp`.
<% end %>
La sintaxis de Mongo es familiar, ya que utiliza una interfaz JavaScript. No vamos a hacer ningún tipo de manipulación de datos adicional en la consola de Mongo, pero podemos echar un vistazo de vez en cuando solo para ver lo que pasa por ahí.
### Colecciones en el lado del cliente
Las colecciones son más interesantes en el lado del cliente. Cuando se declara `Posts = new Mongo.Collection('posts');` en el cliente, lo que se está creando es una *caché local dentro del navegador* de la colección real de Mongo. Cuando decimos que las colecciones del lado del cliente son una "caché", queremos decir que contiene un *subconjunto* de los datos, y ofrece un acceso muy *rápido*.
Es importante entender este punto, ya que es fundamental para comprender la forma en la que funciona Meteor. En general, una colección del lado del cliente consiste en un subconjunto de todos los documentos almacenados en la colección de Mongo (por lo general, nunca querremos enviar *toda nuestra base de datos* al cliente).
En segundo lugar, los documentos se almacenan _en la memoria del navegador_, lo que significa que el acceso a ellos es prácticamente instantáneo. Así que, cuando se llama, por ejemplo, a `Posts.find()` desde el cliente, no hay caminos lentos hasta el servidor o a la base de datos, ya que los datos ya están precargados.
<% note do %>
### Introduciendo MiniMongo
La implementación de Mongo en el lado del cliente de Meteor se llama MiniMongo. Todavía no está implementada por completo y es posible que podamos encontrar algunas características de Mongo que no funcionan en MiniMongo. Sin embargo, todas las que cubrimos en este libro funcionan de manera similar.
<% end %>
### Comunicación cliente-servidor
La parte más importante de todo esto es cómo se sincronizan los datos de la colección del cliente con la colección del mismo nombre (en nuestro caso `posts`) del servidor.
Mejor que explicarlo en detalle, vamos a verlo.
Empezaremos abriendo dos ventanas del navegador, y accediendo a la consola en cada uno de ellos. A continuación, abrimos la consola de Mongo en la línea de comandos.
En este punto, deberíamos ser capaces de encontrar el único documento que hemos creado antes desde la consola de Mongo (ten en cuenta que el *interfaz* de nuestra aplicación estará mostrando todavía los tres posts de prueba anteriores. Ignóralos por ahora).
~~~bash
> db.posts.find();
{title: "A new post", _id: ObjectId("..")};
~~~
<%= caption "Consola de Mongo" %>
~~~js
❯ Posts.findOne();
{title: "A new post", _id: LocalCollection._ObjectID};
~~~
<%= caption "Consola del primer navegador" %>
Creemos un nuevo post en una de las ventanas del navegador ejecutando un insert:
~~~js
❯ Posts.find().count();
1
❯ Posts.insert({title: "A second post"});
'xxx'
❯ Posts.find().count();
2
~~~
<%= caption "Consola del primer navegador" %>
Como era de esperar, el post aparece en la colección local. Ahora vamos a comprobar Mongo:
~~~bash
❯ db.posts.find();
{title: "A new post", _id: ObjectId("..")};
{title: "A second post", _id: 'yyy'};
~~~
<%= caption "Consola de Mongo" %>
Como puedes ver, el post ha viajado hasta la base de datos sin escribir una sola línea de código para enlazar nuestro cliente hasta el servidor (bueno, en sentido estricto, hemos escrito una _sola_ línea de código: `new Mongo.Collection("posts")`). ¡Pero eso no es todo!
Escribamos esto en la consola del segundo navegador:
~~~js
❯ Posts.find().count();
2
~~~
<%= caption "Consola del segundo navegador" %>
¡El post está ahí también! A pesar de que no hemos refrescado ni interactuado con el segundo navegador, y desde luego no hemos escrito código para insertar actualizaciones. Todo ha sucedido por arte de magia -- e instantáneamente. Todo esto se hará más evidente más adelante.
Lo que ha pasado es que la colección del cliente ha informado de un nuevo post a la colección del servidor, que inmediatamente se pone a distribuirlo en la base de datos Mongo y a todos los clientes conectados a la colección `post`.
Ver los posts en la consola del navegador no es muy útil. Vamos a aprender cómo conectar estos datos a nuestras plantillas, y de esta forma, convertir nuestro prototipo HTML en una aplicación web en tiempo real.
### Rellenando la base de datos
Ver el contenido de nuestras colecciones en la consola del navegador es una cosa, pero lo que realmente nos gustaría es mostrar los datos y sus cambios en la pantalla. Cuando esto ocurra, habremos convertido nuestra sencilla *página* web que muestra datos estáticos, en una *aplicación* web en tiempo real en la que los datos cambian de forma dinámica.
Lo primero que vamos a hacer es meter unos cuantos datos en la base de datos. Lo haremos mediante un archivo que carga un conjunto de datos estructurados en la colección de `Posts` cuando el servidor se inicia por primera vez.
En primer lugar, vamos a asegurarnos de que no hay nada en la base de datos. Para borrar la base de datos y restablecer el proyecto usaremos `meteor reset`. Por supuesto, hay que ser muy cuidadoso con este comando una vez que se empieza a trabajar en proyectos del mundo-real.
Paramos el servidor Meteor (pulsando `ctrl-c`) y, a continuación, en la línea de comandos, ejecutamos:
~~~bash
meteor reset
~~~
El comando reset borra completamente la base de datos Mongo. Es útil en el desarrollo cuándo hay bastantes posibilidades de que nuestra base de datos caiga en un estado inconsistente.
Vamos a inciar nuestra aplicación Meteor de nuevo:
~~~bash
meteor
~~~
Ahora que la base de datos está vacía, podemos añadir lo siguiente a `server/fixtures.js` para cargar tres posts cuando el servidor arranca y encuentra la colección `Posts` vacía:
~~~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", "Datos para la colección de posts." %>
Hemos ubicado este archivo en el directorio `/server`, por lo que no se cargará en el navegador de ningún usuario. El código se ejecutará inmediatamente cuando se inicia el servidor, y hará tres llamadas a `insert` para agregar tres sencillos posts en la colección de Posts.
Ahora ejecutamos nuevamente el servidor con `meteor`, y estos tres posts se cargarán en la base de datos.
### Datos dinámicos
Si abrimos una consola de navegador, veremos los tres mensajes cargados desde MiniMongo:
~~~js
❯ Posts.find().fetch();
~~~
<%= caption "Consola del navegador" %>
Para ver estos mensajes renderizados en HTML, podemos utilizar un ayudante de plantilla.
En el Capítulo 3 vimos cómo Meteor nos permite enlazar un *contexto de datos* a nuestras plantillas Spacebars para construir vistas HTML a partir de estructuras de datos simples. Bien, pues, de la misma forma vamos a enlazar los datos de nuestra colección. Simplemente reemplazamos el objeto JavaScript estático `postsData` por una colección dinámica.
A propósito, no dudes en borrar el código de `postsData`. Así es cómo debe quedar `client/templates/posts/posts_list.js`:
~~~js
Template.postsList.helpers({
posts: function() {
return Posts.find();
}
});
~~~
<%= caption "client/templates/posts/posts_list.js" %>
<%= highlight "2~4" %>
<%= commit "4-3", "Conexión entre la colección Posts y la plantilla `postList`." %>
<% note do %>
### Find & Fetch
En Meteor, `find()` devuelve un cursor que es una [fuente de datos reactiva](http://docs.meteor.com/#find). Cuando queramos usar los contenidos a los que apunta el cursor, podemos usar `fetch()` sobre él para trasformarlo en un array.
Dentro de una aplicación, Meteor es lo suficientemente inteligente para saber cómo iterar sobre cursores sin tener que convertirlos de forma explícita en arrays. Por eso no veremos a menudo `fetch()` en el código Meteor (y por eso no lo hemos usado en el ejemplo anterior).
<% end %>
Ahora, en lugar de cargar una lista de mensajes como un array estático desde una variable, ahora estamos devolviendo un cursor a nuestro ayudante `posts` (aunque la cosa no parece muy diferente puesto que estamos devolviendo exactamente los mismos datos):
<%= screenshot "4-3", "Usando datos en vivo" %>
Nuestro ayudante `{{#each}}` ha recorrido todos nuestros `Posts`, y los ha mostrado en la pantalla. La colección del lado del servidor ha tomado los posts de Mongo, los ha pasado a nuestra colección del lado del cliente, y nuestro ayudante Spacebars los ha pasado a la plantilla.
Ahora iremos un paso más allá, y vamos a añadir otro post a través de la consola del navegador:
~~~js
❯ Posts.insert({
title: 'Meteor Docs',
author: 'Tom Coleman',
url: 'http://docs.meteor.com'
});
~~~
<%= caption "Consola del navegador" %>
Vuelve a mirar el navegador -- deberías ver esto:
<%= screenshot "4-4", "Añadiendo un post desde la consola" %>
Acabas de ver la reactividad en acción por primera vez. Cuando le pedimos a Spacebars que recorra el cursor `Posts.find()`, él ya sabe cómo monitorizar este cursor en busca de cambios, y de esa forma, alterar el código HTML para mostrar los datos correctos en la pantalla.
<% note do %>
### Inspeccionando cambios en el DOM
En este caso, el cambio más simple posible es añadir otro `<div class="post"> ... </div>`. Si queremos asegurarnos de que esto es realmente lo que ocurre, solo tenemos que abrir el inspector DOM del navegador y seleccionar el `<div>` correspondiente a uno de los posts existentes.
Ahora, desde la consola, insertamos otro post. Cuando volvemos de nuevo al inspector, podremos ver un `<div>`, correspondiente al nuevo post, pero seguirás teniendo el mismo `<div>` seleccionado. Esta es una manera útil de saber cuándo han vuelto a ser renderizados los elementos y cuándo no.
<% end %>
### Conectando colecciones: Publicaciones y suscripciones
Meteor tiene habilitado por defecto el paquete `autopublish`, algo que no es conveniente para aplicaciones en producción. Este paquete indica que las colecciones son compartidas en su totalidad con cada cliente conectado. Esto no es lo que realmente queremos, así que vamos a deshabilitarlo.
Abrimos una nueva ventana de terminal y escribimos:
~~~bash
meteor remove autopublish
~~~
Esto tiene un efecto instantáneo. Si miramos ahora el navegador, veremos que todos nuestros posts han desaparecido! Esto se debe a que confiábamos en `autopublish` para asegurarnos de que nuestra colección del lado del cliente era una réplica de todos los posts de la base de datos.
Con el tiempo vamos a necesitar asegurarnos de que solo trasferimos los posts que el usuario realmente necesita ver (teniendo en cuenta cosas como la paginación). Pero, por ahora, lo vamos a configurar para que la colección `Posts` se publique en su totalidad (tal y como lo teníamos hasta ahora).
Para ello, creamos una función `publish()` que devuelve un cursor que referencia a todos los posts:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
~~~
<%= caption "server/publications.js" %>
En el cliente, hay que *suscribirse* a la publicación. Añadimos la siguiente línea a `main.js`:
~~~js
Meteor.subscribe('posts');
~~~
<%= caption "client/main.js" %>
<%= commit "4-4", "`autopublish` eliminado y configurada una publicación básica." %>
Si comprobamos el navegador de nuevo, veremos que nuestros posts están de vuelta. ¡Uf!
### Conclusión
Entonces, ¿qué hemos logrado? Bueno, a pesar de que no tenemos interfaz de usuario, lo que tenemos es una aplicación web completamente funcional. Podríamos desplegar esta aplicación en Internet, y (mediante la consola del navegador) empezar a publicar nuevas historias y verlas aparecer en los navegadores de otros usuarios de todo el mundo.