collections - tipos - teoria de conjuntos pdf unam
Publicación/suscripción de subconjuntos múltiples de la misma colección de servidores (3)
¿No podría simplemente usar la misma consulta del lado del cliente cuando quiera mirar los artículos?
En un directorio lib:
enabledItems = function() {
return Items.find({enabled: true});
}
processedItems = function() {
return Items.find({processed: true});
}
En el servidor:
Meteor.publish(''enabled_items'', function() {
return enabledItems();
});
Meteor.publish(''processed_items'', function() {
return processedItems();
});
En el cliente
Meteor.subscribe(''enabled_items'');
Meteor.subscribe(''processed_items'');
Template.enabledItems.items = function() {
return enabledItems();
};
Template.processedItems.items = function() {
return processedItems();
};
Si lo piensas bien, es mejor así como si insertas (localmente) un elemento que está habilitado y procesado, puede aparecer en ambas listas (a diferencia de si tienes dos colecciones separadas).
NOTA
Me di cuenta de que estaba un poco confusa, así que amplié un poco esto, espero que ayude.
EDITAR: esta pregunta, algunas de las respuestas y algunos de los comentarios contienen mucha desinformación. Vea cómo las colecciones, publicaciones y suscripciones de Meteor funcionan para una comprensión precisa de la publicación y la suscripción a múltiples subconjuntos de la misma colección de servidores.
¿Cómo se puede publicar diferentes subconjuntos (o "vistas") de una sola colección en el servidor como múltiples colecciones en el cliente?
Aquí hay un pseudo código para ayudar a ilustrar mi pregunta:
colección de items
en el servidor
Supongamos que tengo una colección de items
en el servidor con millones de registros. Supongamos también que:
- 50 registros tienen la propiedad
enabled
establecida entrue
, y; - 100 registros tienen la propiedad
processed
establecida entrue
.
Todos los demás están configurados como false
.
items:
{
"_id": "uniqueid1",
"title": "item #1",
"enabled": false,
"processed": false
},
{
"_id": "uniqueid2",
"title": "item #2",
"enabled": false,
"processed": true
},
...
{
"_id": "uniqueid458734958",
"title": "item #458734958",
"enabled": true,
"processed": true
}
Código del servidor
Publiquemos dos "vistas" de la misma colección de servidores. Uno enviará un cursor con 50 registros, y el otro enviará un cursor con 100 registros. Hay más de 458 millones de registros en esta base de datos ficticia del lado del servidor, y el cliente no necesita saber acerca de todos ellos (de hecho, enviarlos a todos probablemente llevaría varias horas en este ejemplo):
var Items = new Meteor.Collection("items");
Meteor.publish("enabled_items", function () {
// Only 50 "Items" have enabled set to true
return Items.find({enabled: true});
});
Meteor.publish("processed_items", function () {
// Only 100 "Items" have processed set to true
return Items.find({processed: true});
});
Codigo del cliente
Para admitir la técnica de compensación de latencia, nos vemos obligados a declarar una única colección de Items
en el cliente. Debería hacerse evidente dónde se encuentra el defecto: ¿cómo se diferencia entre los Items
para los Items
enabled_items
y los Items
para los Items
processed_items
?
var Items = new Meteor.Collection("items");
Meteor.subscribe("enabled_items", function () {
// This will output 50, fine
console.log(Items.find().count());
});
Meteor.subscribe("processed_items", function () {
// This will also output 50, since we have no choice but to use
// the same "Items" collection.
console.log(Items.find().count());
});
Mi solución actual incluye el parche de monos _publishCursor para permitir el uso del nombre de la suscripción en lugar del nombre de la colección. Pero eso no hará ninguna compensación de latencia. Cada escritura tiene que ser ida y vuelta al servidor:
// On the client:
var EnabledItems = new Meteor.Collection("enabled_items");
var ProcessedItems = new Meteor.Collection("processed_items");
Con el parche de mono en su lugar, esto funcionará. Pero acceda al modo fuera de línea y los cambios no aparecerán en el cliente de inmediato; tendremos que estar conectados al servidor para ver los cambios.
¿Cuál es el enfoque correcto?
EDITAR: Acabo de volver a visitar este hilo y me doy cuenta de que, tal como está, mis preguntas y respuestas y una plétora de comentarios llevan mucha información errónea.
Lo que se reduce a eso es que no entiendo bien la relación publicar-suscribir. Pensé que cuando publicabas un cursor, caía en el cliente como una colección separada de otros cursores publicados que se originaban en la misma colección de servidores. Esto simplemente no es como funciona. La idea es que tanto el cliente como el servidor tengan las mismas colecciones, pero es lo que está en las colecciones lo que difiere. Los contratos pub-sub negocian qué documentos terminan en el cliente. La respuesta de Tom es técnicamente correcta, pero me faltan algunos detalles para cambiar mis suposiciones. Respondí una pregunta similar a la mía en otro hilo SO basado en la explicación de Tom, pero teniendo en cuenta mi incomprensión original de la publicación de Meteor : Meteor publicar / suscribir estrategias para colecciones exclusivas del lado del cliente
Espero que esto ayude a aquellos que se encuentran con este hilo y salen más confundidos que nada.
Logré lograr algunos resultados preliminares prometedores abordando el problema con una única publicación / suscripción por colección, y aprovechando $or
en la consulta de búsqueda.
La idea es proporcionar una envoltura alrededor de Meteor.Collection
que te permita agregar "vistas", que son básicamente cursores. Pero lo que realmente está sucediendo es que estos cursores no se ejecutan individualmente ... sus selectores se extraen, se juntan $ y se ejecutan como una sola consulta y en un solo pub-sub.
No es perfecto, ya que un offset / límite no funcionará con esta técnica, pero en este momento minimongo no lo admite de todos modos.
Pero, en última instancia, lo que le permite hacer es declarar lo que parecen subconjuntos diferentes de la misma colección, pero bajo el capó son el mismo subconjunto. Hay un poco de abstracción al frente para hacer que se sientan limpiamente separados.
Ejemplo:
// Place this code in a file read by both client and server:
var Users = new Collection("users");
Users.view("enabledUsers", function (collection) {
return collection.find({ enabled: true }, { sort: { name: 1 } });
});
O si quieres pasar parámetros:
Users.view("filteredUsers", function (collection) {
return collection.find({ enabled: true, name: this.search, { sort: { name: 1 } });
}, function () {
return { search: Session.get("searchterms"); };
});
Los parámetros se dan como objetos, porque es un solo publicar / suscribir $ or''d juntos, necesitaba una forma de obtener los parámetros correctos ya que se mezclan.
Y para usarlo en una plantilla:
Template.main.enabledUsers = function () {
return Users.get("enabledUsers");
};
Template.main.filteredUsers = function () {
return Users.get("filteredUsers");
};
En resumen, aprovecho para tener el mismo código ejecutándose tanto en el servidor como en el cliente, y si el servidor no está haciendo algo, el cliente lo hará, o viceversa.
Y lo más importante, solo los registros que le interesan se envían al cliente. Todo esto se puede lograr sin una capa de abstracción simplemente haciendo $ o usted mismo, pero ese $ o se pondrá bastante feo a medida que se agreguen más subconjuntos. Esto solo ayuda a administrarlo con un código mínimo.
Escribí esto rápidamente para probarlo, disculpas por la duración y la falta de documentación:
test.js
// Shared (client and server)
var Collection = function () {
var SimulatedCollection = function () {
var collections = {};
return function (name) {
var captured = {
find: [],
findOne: []
};
collections[name] = {
find: function () {
captured.find.push(([]).slice.call(arguments));
return collections[name];
},
findOne: function () {
captured.findOne.push(([]).slice.call(arguments));
return collections[name];
},
captured: function () {
return captured;
}
};
return collections[name];
};
}();
return function (collectionName) {
var collection = new Meteor.Collection(collectionName);
var views = {};
Meteor.startup(function () {
var viewName, view, pubName, viewNames = [];
for (viewName in views) {
view = views[viewName];
viewNames.push(viewName);
}
pubName = viewNames.join("__");
if (Meteor.publish) {
Meteor.publish(pubName, function (params) {
var viewName, view, selectors = [], simulated, captured;
for (viewName in views) {
view = views[viewName];
// Run the query callback but provide a SimulatedCollection
// to capture what is attempted on the collection. Also provide
// the parameters we would be passing as the context:
if (_.isFunction(view.query)) {
simulated = view.query.call(params, SimulatedCollection(collectionName));
}
if (simulated) {
captured = simulated.captured();
if (captured.find) {
selectors.push(captured.find[0][0]);
}
}
}
if (selectors.length > 0) {
return collection.find({ $or: selectors });
}
});
}
if (Meteor.subscribe) {
Meteor.autosubscribe(function () {
var viewName, view, params = {};
for (viewName in views) {
view = views[viewName];
params = _.extend(params, view.params.call(this, viewName));
}
Meteor.subscribe.call(this, pubName, params);
});
}
});
collection.view = function (viewName, query, params) {
// Store in views object -- we will iterate over it on startup
views[viewName] = {
collectionName: collectionName,
query: query,
params: params
};
return views[viewName];
};
collection.get = function (viewName, optQuery) {
var query = views[viewName].query;
var params = views[viewName].params.call(this, viewName);
if (_.isFunction(optQuery)) {
// Optional alternate query provided, use it instead
return optQuery.call(params, collection);
} else {
if (_.isFunction(query)) {
// In most cases, run default query
return query.call(params, collection);
}
}
};
return collection;
};
}();
var Items = new Collection("items");
if (Meteor.isServer) {
// Bootstrap data -- server only
Meteor.startup(function () {
if (Items.find().count() === 0) {
Items.insert({title: "item #01", enabled: true, processed: true});
Items.insert({title: "item #02", enabled: false, processed: false});
Items.insert({title: "item #03", enabled: false, processed: false});
Items.insert({title: "item #04", enabled: false, processed: false});
Items.insert({title: "item #05", enabled: false, processed: true});
Items.insert({title: "item #06", enabled: true, processed: true});
Items.insert({title: "item #07", enabled: false, processed: true});
Items.insert({title: "item #08", enabled: true, processed: false});
Items.insert({title: "item #09", enabled: false, processed: true});
Items.insert({title: "item #10", enabled: true, processed: true});
Items.insert({title: "item #11", enabled: true, processed: true});
Items.insert({title: "item #12", enabled: true, processed: false});
Items.insert({title: "item #13", enabled: false, processed: true});
Items.insert({title: "item #14", enabled: true, processed: true});
Items.insert({title: "item #15", enabled: false, processed: false});
}
});
}
Items.view("enabledItems", function (collection) {
return collection.find({
enabled: true,
title: new RegExp(RegExp.escape(this.search1 || ""), "i")
}, {
sort: { title: 1 }
});
}, function () {
return {
search1: Session.get("search1")
};
});
Items.view("processedItems", function (collection) {
return collection.find({
processed: true,
title: new RegExp(RegExp.escape(this.search2 || ""), "i")
}, {
sort: { title: 1 }
});
}, function () {
return {
search2: Session.get("search2")
};
});
if (Meteor.isClient) {
// Client-only templating code
Template.main.enabledItems = function () {
return Items.get("enabledItems");
};
Template.main.processedItems = function () {
return Items.get("processedItems");
};
// Basic search filtering
Session.get("search1", "");
Session.get("search2", "");
Template.main.search1 = function () {
return Session.get("search1");
};
Template.main.search2 = function () {
return Session.get("search2");
};
Template.main.events({
"keyup [name=''search1'']": function (event, template) {
Session.set("search1", $(template.find("[name=''search1'']")).val());
},
"keyup [name=''search2'']": function (event, template) {
Session.set("search2", $(template.find("[name=''search2'']")).val());
}
});
Template.main.preserve([
"[name=''search1'']",
"[name=''search2'']"
]);
}
// Utility, shared across client/server, used for search
if (!RegExp.escape) {
RegExp.escape = function (text) {
return text.replace(/[-[/]{}()*+?.,//^$|#/s]/g, "//$&");
};
}
test.html
<head>
<title>Collection View Test</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Collection View Test</h1>
<div style="float: left; border-right: 3px double #000; margin-right: 10px; padding-right: 10px;">
<h2>Enabled Items</h2>
<input type="text" name="search1" value="{{search1}}" placeholder="search this column" />
<ul>
{{#each enabledItems}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
<div style="float: left;">
<h2>Processed Items</h2>
<input type="text" name="search2" value="{{search2}}" placeholder="search this column" />
<ul>
{{#each processedItems}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
</template>
podrías hacer dos publicaciones separadas como esta ...
Publicaciones del servidor
Meteor.publish("enabled_items", function(){
var self = this;
var handle = Items.find({enabled: true}).observe({
added: function(item){
self.set("enabled_items", item._id, item);
self.flush();
},
changed: function(item){
self.set("enabled_items", item._id, item);
self.flush();
}
});
this.onStop(function() {
handle.stop();
});
});
Meteor.publish("disabled_items", function(){
var self = this;
var handle = Items.find({enabled: false}).observe({
added: function(item){
self.set("disabled_items", item._id, item);
self.flush();
},
changed: function(item){
self.set("disabled_items", item._id, item);
self.flush();
}
});
this.onStop(function() {
handle.stop();
});
});
Suscripciones de clientes
var EnabledItems = new Meteor.Collection("enabled_items"),
DisabledItems = new Meteor.Collection("disabled_items");
Meteor.subscribe("enabled_items");
Meteor.subscribe("disabled_items");