Publicaciones avanzadas

Sidebar 13.5

Porcentaje completado

En este capítulo:

  • Aprenderemos patrones más avanzados para manipular publicaciones.
  • Veremos lo flexibles que son publicaciones y suscripciones.
  • A estas alturas ya deberías conocer bastante bien cómo interactúan las suscripciones y las publicaciones. Así que vamos a deshacernos de las ruedas de entrenamiento y examinar algunos escenarios más avanzados.

    Publicar una Colección Varias Veces

    En la primera sidebar, vimos algunos de los patrones más comunes de publicación y suscripción, y aprendimos cómo la función _publishCursor las hace muy fáciles de usar en nuestras aplicaciones.

    Primero, vamos a recapitular exactamente qué hace por nosotros la función _publishCursor: lo que hace es tomar todos los documentos que coinciden con un cursor dado, y los envía a la colección del cliente del mismo nombre. Ten en cuenta que el nombre de la publicación no está involucrado.

    Esto significa que podemos tener más de una publicación de cualquier colección, que enlaza al cliente y al servidor

    Ya hemos encontrado este patrón en el capítulo paginación, cuando publicamos un subconjunto paginado de todos los posts, además del post actual.

    Otro caso de uso similar es publicar una overview de un gran conjunto de documentos, así como también los detalles completos de un único ítem:

    Publishing a collection twice
    Publishing a collection twice
    Meteor.publish('allPosts', function() {
      return Posts.find({}, {fields: {title: true, author: true}});
    });
    
    Meteor.publish('postDetail', function(postId) {
      return Posts.find(postId);
    });
    

    Ahora cuando el cliente se suscriba a esas dos publicaciones, su colección 'posts' se rellena desde dos fuentes: una lista de títulos y nombres de autor de la primera suscripción, y los detalles completos de un único post de la segunda.

    Tal vez te hayas dado cuenta de que el post publicado por postDetail se publica también desde allPosts (aunque solo con un subconjunto de sus propiedades). Sin embargo, Meteor se hace cargo de la superposición fusionando los campos y asegurando que no haya duplicados.

    Esto es genial, porque ahora cuando renderizemos la lista de los resúmenes de los posts, estaremos lidiando con objetos de datos que tienen lo suficiente para mostrar lo que necesitamos. Y cuando renderizemos la página de un sólo post, tambén lo tenemos. Por supuesto, tenemos que ocuparnos que el cliente no espere que todos los campos estén disponibles en todos los posts – ¡esto es un error común!

    Ten en cuenta que no estamos limitados a variar las propiedades de los documentos. Podríamos también, publicar las mismas propiedades en ambas publicaciones, pero ordenar los items de otra manera.

    Meteor.publish('newPosts', function(limit) {
      return Posts.find({}, {sort: {submitted: -1}, limit: limit});
    });
    
    Meteor.publish('bestPosts', function(limit) {
      return Posts.find({}, {sort: {votes: -1, submitted: -1}, limit: limit});
    });
    
    server/publications.js

    Suscribiéndose a una Publicación Múltiples Veces

    Acabamos de ver cómo se puede publicar una sola colección más de una vez. Resulta que se puede lograr un resultado muy similar con otro patrón: creando una única publicación, pero suscribiéndonos a ella varias veces.

    En Microscope, nos suscribimos a la publicación de posts varias veces, pero Iron Router activa y desactiva cada suscripción por nosotros. Aun así, no hay ninguna razón por la cual no podamos suscribirnos muchas veces simultáneamente.

    Por ejemplo, digamos que queremos cargar los posts más recientes y los mejores en la memoria al mismo tiempo:

    Subscribing twice to one publication
    Subscribing twice to one publication

    Estamos estableciendo una sola publicación:

    Meteor.publish('posts', function(options) {
      return Posts.find({}, options);
    });
    

    Luego, nos suscribimos a esta publicación múltiples veces. De hecho, esto es más o menos exactamente lo que estamos haciendo en Microscope:

    Meteor.subscribe('posts', {submitted: -1, limit: 10});
    Meteor.subscribe('posts', {baseScore: -1, submitted: -1, limit: 10});
    

    Entonces, ¿qué está pasando exactamente? Cada navegador abre dos suscripciones diferentes, cada uno se conecta a la misma publicación en el servidor.

    Cada suscripción ofrece diferentes argumentos para esa publicación, pero fundamentalmente, cada vez que un conjunto de documentos (diferente) se saca de la colección posts, se envía por el canal a la colección del lado del cliente.

    Puedes incluso suscribirte dos veces a la misma publicación con ¡los mismos argumentos! Es difícil pensar en escenarios donde esto sea de utilidad, ¡pero esta flexibilidad puede que sea útil algún día!

    Múltiples Colecciones sobre una Única Suscripción

    A diferencia de las bases de datos relacionales tradicionales como MySQL que hacen uso de joins , las bases de datos NoSQL como Mongo se basan en la denormalización e incrustación. Vamos a ver cómo funciona en el contexto de Meteor.

    Veamos un ejemplo concreto. Hemos añadido comentarios a nuestros posts, y hasta ahora, hemos publicado los comentarios en la página de un único post.

    Sin embargo, supongamos que queremos mostrar todos los comentarios en los posts en la página principal (teniendo en cuenta que estos posts van a cambiar a medida que paginamos a través de ellos). Este caso de uso presenta una buena razón para insertar comentarios en los posts, y de hecho es lo que nos empuja a desnormalizar la colección de comentarios.

    Por supuesto que siempre se pueden insertar los comentarios en los posts, deshaciéndonos de la colección de comments por completo. Pero, como hemos visto anteriormente en el capítulo Desnormalización, al hacerlo estaríamos perdiendo algunos beneficios adicionales de trabajar con colecciones separadas.

    Pero resulta que hay un truco que hace posible embeber nuestros comentarios, preservando colecciones separadas.

    Supongamos que, junto con la lista de la página principal de posts, queremos suscribirnos a una lista de los mejores 2 comentarios para cada uno de ellos.

    Lograr esto con una publicación de comentarios independiente sería difícil, sobre todo si la relación de posts se limita de alguna manera (por ejemplo, los 10 más recientes). Tendríamos que crear una publicación que se pareciera a algo como esto:

    Two collections in one subscription
    Two collections in one subscription
    Meteor.publish('topComments', function(topPostIds) {
      return Comments.find({postId: topPostIds});
    });
    

    Esto sería un problema desde el punto de vista de rendimiento, ya que habría que eliminar y volver a establecer la publicación cada vez que cambiara la lista de topPostIds.

    Existe una manera de evitar esto. Acabamos de utilizar el hecho de que no solo podemos tener más de una publicación por colección, sino que también podemos tener más de una colección por publicación:

    Meteor.publish('topPosts', function(limit) {
      var sub = this, commentHandles = [], postHandle = null;
    
      // send over the top two comments attached to a single post
      function publishPostComments(postId) {
        var commentsCursor = Comments.find({postId: postId}, {limit: 2});
        commentHandles[postId] =
          Mongo.Collection._publishCursor(commentsCursor, sub, 'comments');
      }
    
      postHandle = Posts.find({}, {limit: limit}).observeChanges({
        added: function(id, post) {
          publishPostComments(id);
          sub.added('posts', id, post);
        },
        changed: function(id, fields) {
          sub.changed('posts', id, fields);
        },
        removed: function(id) {
          // stop observing changes on the post's comments
          commentHandles[id] && commentHandles[id].stop();
          // delete the post
          sub.removed('posts', id);
        }
      });
    
      sub.ready();
    
      // make sure we clean everything up (note `_publishCursor`
      //   does this for us with the comment observers)
      sub.onStop(function() { postHandle.stop(); });
    });
    

    Tengamos en cuenta que no estamos devolviendo nada en esta publicación, le enviamos mensajes manualmente a la sub nosotros mismos (a través de .added() y sus amigos). Así que no necesitamos que _publishCursor lo haga mediante la devolución de un cursor.

    Ahora, cada vez que publiquemos un post también publicaremos automáticamente los dos primeros comentarios adjuntos. ¡Y todo con una sola llamada a una suscripción!

    Aunque Meteor aún no hace este enfoque muy sencillo, también se puede utilizar el paquete publish-with-relations de Atmosphere, cuyo objetivo es hacer que este patrón sea más fácil de usar.

    Enlazando colecciones diferentes

    ¿Qué mas puede darnos nuestro nuevo conocimiento sobre la flexibilidad de las suscripciones? Bueno, si no usamos _publishCursor, no tendremos la restricción de que la fuente de la colección en el servidor necesita tener el mismo nombre que la colección de destino en el cliente.

    One collection for two subscriptions
    One collection for two subscriptions

    Una razón por la que no querríamos hacer esto es la Herencia de Tabla Simple

    Supongamos que quisiéramos referenciar varios tipos de objetos desde nuestros posts, cada uno alojado en campos comunes pero ligeramente diferentes en contenido. Por ejemplo, podríamos estar creando un motor de blogging al estilo de Tumblr en el que cada post posee el habitual ID, un timestamp, y el título. Pero también puede tener imágenes, videos, links o simplemente texto.

    Podríamos guardar todos estos objetos en una colección llamada 'resources' (recursos), usando un atributo type que indique qué tipo de objeto son (video, image, link, etc.).

    Y aunque tendríamos una sola colección resources en el servidor, podríamos transformar esa única colección en múltiples colecciones en el cliente, como Videos, Images, etc., con el siguiente trozo de magia:

      Meteor.publish('videos', function() {
        var sub = this;
    
        var videosCursor = Resources.find({type: 'video'});
        Mongo.Collection._publishCursor(videosCursor, sub, 'videos');
    
        // _publishCursor doesn't call this for us in case we do this more than once.
        sub.ready();
      });
    

    Le estamos diciendo a _publishCursor que publique nuestros videos (como hacer un return) como lo haría el cursor, pero en lugar de publicar la colección resources en el cliente, publicamos de resources a videos.

    Otra idea similar es usar publish para una colección en el lado del cliente donde ¡no hay ninguna colección en el lado servidor!. Por ejemplo, podrías obtener datos de un servicio de terceros, y publicarlos como si fuera una colección en el cliente.

    Gracias a la flexibilidad de la API de publicación, las posibilidades son ilimitadas.