Publikowanie / subskrybowanie wielu podzbiorów tej samej kolekcji serwerów

EDIT: to pytanie, niektóre odpowiedzi i niektóre komentarze zawierają wiele dezinformacji. Zobacz Jak działają Kolekcje, publikacje i subskrypcje Meteor, aby dokładnie zrozumieć publikowanie i subskrybowanie wielu podzbiorów tej samej kolekcji serwerów.


Jak można publikować różne podzbiory (lub" widoki") pojedynczej kolekcji na serwerze jako wiele kolekcji na kliencie?

Oto kilka pseudo-kod, który pomoże zilustrować moje pytanie:

items kolekcja na serwerze

Załóżmy, że mam na serwerze kolekcję items z milionami rekordów. Załóżmy również, że:

  1. 50 rekordów ma właściwość enabled ustawioną na true oraz;
  2. 100 rekordów ma właściwość processed ustawioną na true.

Wszystkie pozostałe są ustawione na 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
}

Kod serwera

Opublikujmy dwa "widoki" tej samej kolekcji serwerów. Jeden wyśle kursor z 50 rekordami, a drugi wyśle kursor z 100 rekordami. Istnieje ponad 458 milionów rekordów w tej fikcyjnej bazie danych po stronie serwera, a Klient nie musi wiedzieć o nich wszystkich (w rzeczywistości wysłanie ich wszystkich zajęłoby prawdopodobnie kilka godzin w tym przykładzie): {]}

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});
});

Kod klienta

W celu wsparcia techniki kompensacji latencji, jesteśmy zmuszeni zadeklarować pojedynczy zbiór Items na kliencie. Powinno stać się jak rozróżnić Items dla enabled_items i Items dla 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());
});

Moje obecne rozwiązanie polega na łataniu _publishCursor, aby umożliwić użycie nazwy subskrypcji zamiast nazwy kolekcji. Ale to nie zrekompensuje opóźnienia. Każdy zapis musi być przekierowany na Serwer:

// On the client:
var EnabledItems = new Meteor.Collection("enabled_items");
var ProcessedItems = new Meteor.Collection("processed_items");

Z małpą na miejscu, to zadziała. Ale przejść w tryb Offline i zmiany nie pojawią się na kliencie od razu-będziemy musisz być podłączony do serwera, aby zobaczyć zmiany.

Jakie jest właściwe podejście?


EDIT: właśnie przejrzałem ten wątek i zdałem sobie sprawę, że moje pytania i Odpowiedzi oraz mnóstwo komentarzy niosą ze sobą wiele dezinformacji.

Sprowadza się to do tego, że źle zrozumiałem relację publikuj-Subskrybuj. Myślałem, że kiedy opublikujesz kursor, wyląduje on na kliencie jako oddzielna kolekcja od innych opublikowanych kursorów, które powstały z tej samej kolekcji serwerów. Po prostu tak to nie działa. Chodzi o to, że zarówno klient, jak i serwer mają te same kolekcje, ale to, co jest w kolekcjach, które się różnią. Umowy pub-sub negocjują, które dokumenty kończą się na kliencie. Odpowiedź Toma jest technicznie poprawna, ale brakowało kilku szczegółów, aby zmienić moje założenia. Odpowiedziałem na podobne pytanie do mojego w innym tak wątku opartym na wyjaśnieniu Toma, ale mając na uwadze mój oryginalny firma Meteor ' s pub-sub: Meteor publikuj / Subskrybuj unikalne kolekcje po stronie klienta

Mam nadzieję, że to pomoże tym, którzy natkną się na ten wątek i odejdą bardziej zdezorientowani niż cokolwiek innego!

Author: Community, 2012-09-28

3 answers

Czy nie możesz po prostu użyć tego samego zapytania po stronie klienta, gdy chcesz spojrzeć na elementy?

W katalogu lib:

enabledItems = function() {
  return Items.find({enabled: true});
}
processedItems = function() {
  return Items.find({processed: true});
}

Na serwerze:

Meteor.publish('enabled_items', function() {
  return enabledItems();
});
Meteor.publish('processed_items', function() {
  return processedItems();
});

Na kliencie

Meteor.subscribe('enabled_items');
Meteor.subscribe('processed_items');

Template.enabledItems.items = function() {
  return enabledItems();
};
Template.processedItems.items = function() {
  return processedItems();
};

Jeśli się nad tym zastanowić, to lepiej tak, jakby wstawić (lokalnie) element, który jest zarówno włączony, jak i przetworzony, może pojawić się w obu listach(a w przeciwieństwie do Jeśli masz dwie oddzielne Kolekcje).

Uwaga

Zdałem sobie sprawę, że jestem trochę niejasny, więc rozszerzyłem to trochę, mam nadzieję, że to pomoże.

 33
Author: Tom Coleman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 03:19:45

Można by zrobić dwie osobne publikacje, takie jak ta..

Publikacje serwera

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();
    });
});

Subskrypcje Klientów

var EnabledItems = new Meteor.Collection("enabled_items"),
    DisabledItems = new Meteor.Collection("disabled_items");

Meteor.subscribe("enabled_items");
Meteor.subscribe("disabled_items");
 6
Author: Lloyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-30 18:16:02

Udało mi się osiągnąć obiecujące wstępne wyniki, podchodząc do problemu z pojedynczą publikacją/subskrypcją na kolekcję i wykorzystując $or w zapytaniu find.

Chodzi o to, aby zapewnić owijarkę wokół Meteor.Collection, która pozwala na dodawanie "widoków", które są w zasadzie nazwane kursorami. Ale to, co się naprawdę dzieje, to to, że te Kursory nie są uruchamiane indywidualnie... ich selektory są wyodrębniane, $or ' D razem i uruchamiane jako pojedyncze zapytanie i na jednym pub-sub.

To Nie idealnie, w tym, że offset / limit nie zadziała z tą techniką, ale w tej chwili minimongo i tak jej nie wspiera.

Ale ostatecznie to, co pozwala Ci zrobić, to zadeklarować to, co wygląda jak różne podzbiory tego samego zbioru, ale pod maską są one tym samym podzbiorem. Jest tylko trochę abstrakcji z przodu, aby czuć się czysto oddzielone.

Przykład:

// 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 } });
});

Lub jeśli chcesz przekazać parametry:

Users.view("filteredUsers", function (collection) {
    return collection.find({ enabled: true, name: this.search, { sort: { name: 1 } });
}, function () {
    return { search: Session.get("searchterms"); };
});

Parametry podane są jako obiekty, ponieważ jest to pojedynczy publish / subscribe $or ' D razem, potrzebowałem sposobu, aby uzyskać właściwe parametry, ponieważ są mieszane razem.

I faktycznie użyć go w szablonie:

Template.main.enabledUsers = function () {
    return Users.get("enabledUsers");
};
Template.main.filteredUsers = function () {
    return Users.get("filteredUsers");
};

Krótko mówiąc, korzystam z tego, że ten sam kod działa zarówno na serwerze, jak i kliencie, a jeśli serwer czegoś nie robi, klient to zrobi lub odwrotnie.

I co najważniejsze, tylko pliki, które Cię interesują, są wysyłane do klienta. To wszystko jest osiągalne bez warstwy abstrakcji, po prostu robiąc $lub siebie, ale to $ or będzie dość brzydkie, gdy więcej podzbiorów zostanie dodanych. To po prostu pomaga zarządzać nim przy minimalnym kodzie.

Napisałem to szybko, aby przetestować, przepraszam za długość i brak dokumentacji:

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>
 1
Author: matb33,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-02 03:46:42