コレクションごとに 1 つのパブリッシュ/サブスクライブで問題に取り組み、$or
クエリを活用するfind
ことで、いくつかの有望な予備結果を得ることができました。
Meteor.Collection
基本的にはカーソルという名前の「ビュー」を追加できるラッパーを提供するという考え方です。しかし、実際に起こっているのは、これらのカーソルが個別に実行されないということです...それらのセレクターが抽出され、一緒に $or され、単一のクエリとして単一の pub-sub に対して実行されます。
この手法ではオフセット/制限が機能しないため、完全ではありませんが、現時点では、とにかく minimongo はサポートしていません。
しかし最終的にできることは、同じコレクションの異なるサブセットのように見えるものを宣言することですが、内部では同じサブセットです。それらがきれいに分離されているように感じさせるために、前に少しだけ抽象化があります.
例:
// 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 } });
});
または、パラメータを渡したい場合:
Users.view("filteredUsers", function (collection) {
return collection.find({ enabled: true, name: this.search, { sort: { name: 1 } });
}, function () {
return { search: Session.get("searchterms"); };
});
パラメーターはオブジェクトとして与えられます。これは単一のパブリッシュ/サブスクライブ $or'd であり、それらが混在するため、適切なパラメーターを取得する方法が必要でした。
テンプレートで実際に使用するには、次のようにします。
Template.main.enabledUsers = function () {
return Users.get("enabledUsers");
};
Template.main.filteredUsers = function () {
return Users.get("filteredUsers");
};
要するに、私はサーバーとクライアントの両方で同じコードを実行することを利用しています。サーバーが何かを実行していない場合は、クライアントが実行するか、またはその逆です。
そして最も重要なことは、関心のあるレコードのみがクライアントに送信されることです。これは、抽象レイヤーなしで $or を自分で実行するだけですべて達成できますが、サブセットが追加されるにつれて、その $or はかなり醜くなります。これは、最小限のコードで管理するのに役立ちます。
私はこれをテストするために簡単に書きました。長さとドキュメントの欠如をお詫びします。
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>