Colecciones

4

Porcentaje completado

En este capítulo:

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

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

    Commit 4-1

    Colección Posts

    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.

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

    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

    La 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

    La consola del navegador
    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

    La consola de Meteor
    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

    La consola de Mongo
    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.

    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:

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

    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.

    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.

    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.

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

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    Consola de Mongo
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    Consola del primer navegador

    Creemos un nuevo post en una de las ventanas del navegador ejecutando un insert:

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    Consola del primer navegador

    Como era de esperar, el post aparece en la colección local. Ahora vamos a comprobar Mongo:

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

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

    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:

    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:

    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

    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:

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

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

    Commit 4-3

    Conexión entre la colección Posts y la plantilla `postList`.

    Find & Fetch

    En Meteor, find() devuelve un cursor que es una fuente de datos reactiva. 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).

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

    Usando datos en vivo
    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:

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

    Vuelve a mirar el navegador – deberías ver esto:

    Añadiendo un post desde la consola
    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.

    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.

    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:

    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:

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

    En el cliente, hay que suscribirse a la publicación. Añadimos la siguiente línea a main.js:

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

    Commit 4-4

    `autopublish` eliminado y configurada una publicación bás…

    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.