-
Notifications
You must be signed in to change notification settings - Fork 30
/
13s-advanced-publications.md.erb
190 lines (128 loc) · 10.9 KB
/
13s-advanced-publications.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
---
title: Publicaciones avanzadas
slug: advanced-publications
date: 0013/01/02
number: 13.5
points: 10
sidebar: true
photoUrl: http://www.flickr.com/photos/ikewinski/8390558986/
photoAuthor: Mike Lewinski
contents: Aprenderemos patrones más avanzados para manipular publicaciones.|Veremos lo flexibles que son publicaciones y suscripciones.
paragraphs: 36
---
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](/chapters/publications-and-subscriptions/), 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](/chapters/pagination/), 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:
<%= diagram "doublecollection", "Publishing a collection twice", "pull-center" %>
~~~js
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.
~~~js
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});
});
~~~
<%= caption "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:
<%= diagram "subscribetwice", "Subscribing twice to one publication", "pull-center" %>
Estamos estableciendo una sola publicación:
~~~js
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:
~~~js
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 _desnormalizació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:
<%= diagram "multiplecollections", "Two collections in one subscription", "pull-center" %>
~~~js
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_:
~~~js
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.
<%= diagram "linkedcollections", "One collection for two subscriptions", "pull-center" %>
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:
~~~js
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.