Meteor.js: having multiple subscriptions on one collection enforce storage results in one local collection(Workarounds?) - meteor

Is there a way to store subscriptions of the same server collection in a different minimongo collection?
If not is there any best practice to work around?
I do have a summary table having 50k datasets with a lot of details in the documents.
// Server
var collection = new Meteor.Collection("collection");
Meteor.publish("detail", function (id) {
return collection.find({_id: id});
});
// A pager that does not include the data (fields:{data:0})
Meteor.publish("master", function (filter, sort, skip, limit) {
return collection.find({name: new RegExp("^" + filter + "|\\s" + filter, "i")}, {limit: limit, skip: skip, sort: options, fields: {data: 0}});
});
// Client
var collection = new Meteor.Collection("collection");
Deps.autorun(function () {
Meteor.subscribe("master",
Session.get("search"),
Session.get("sort"),
Session.get("skip"),
Session.get("limit")
);
Meteor.subscribe("detail", Session.get("selection"));
});
Problem above: both subscriptions are feed into the same collection.
This does not work well if the results of the finds are stored in the same local collection.
Having a local collection with the name of the subscription/publish would be great.
// Client
var detail = new Meteor.Collection("detail"),
master = new Meteor.Collection("master");
Any Ideas?

If you want your client side collection to have a different name from the server side collection you can not just return a collection cursor. This can be done in the publish function though like this:
Meteor.publish("details", function (id) { //details here matches the subscribe request
var self = this;
self.added( "details", id, collection.findOne({_id: id}); //details here tells the client which collection holds the data
self.ready();
});
This will not be reactive but can be made that way by using observe as in the counts by room example at http://docs.meteor.com which is explained in detail here How does the messages-count example in Meteor docs work?.
While this answers your question of how to get a specific name for a collection without having that collection on the server. I think you probably get what you want more easily with a publish function more like this:
Meteor.publish("master", function (filter, sort, skip, limit, id) {
return [
collection.find({name: new RegExp("^" + filter + "|\\s" + filter, "i")}, {limit: limit, skip: skip, sort: options, fields: {data: 0}})
, collection.find( id , {fields: {data: 1}} )
];
});
Then subscribe on client:
Deps.autorun(function () {
Meteor.subscribe("master",
Session.get("search"),
Session.get("sort"),
Session.get("skip"),
Session.get("limit"),
Session.get("selection")
);
});
Then even though all your data is in one collection you can have a reactive cursor to your selected id with the data included. Query from the client like this:
collection.find( Session.get("selection") );

Related

Search multiple field in MongoDB with MeteorJS

I am beginner in programming and interested to learn MeteorJS. I want to search category_name and subcategory_name by keyword or alphabet.:)
This is my code.
collections: subcategory
{
_id:"ZbwCsJEMi2DesyJA7",
category_name: "ICT",
subcategory_name: "Laptop"
}
subcategory.js
Template.Subcategory.events({
"keyup .searchbox": function(event){
var query = event.target.value;
Session.set('query', query);
}
});
Template.Subcategory.helpers({
subcategory: function(){
var filter = {sort: {}};
var query = Session.get('query');
filter.sort[Session.get('sortby')] = 1;
return Subcategory.find({ subcategory_name: new RegExp(query, 'i')} , filter );
}
});
Seems like you are looking for $or operator:
var queryRegexp = new RegExp(query, 'i');
return Subcategory.find({
$or: [
{ category_name: queryRegexp },
{ subcategory_name: queryRegexp }
]
} , filter);
Official Mongo's documentation for $or: https://docs.mongodb.org/manual/reference/operator/query/or/
You'll have to create a text index in Mongo. You can do this in Meteor still I think with something like this:
MyCollection._ensureIndex({
"$**": "text"
});
The above uses a wildcard but you can be more specific. See https://docs.mongodb.org/v3.0/core/index-text/ for more info.
For larger collections I tend to pass a text search query through a subscriptions so it can be performed on the server.
Assuming you want to show a list of things and allow users to filter them with the search box, you could do something like this:
Meteor.publish('MyCollection', function (searchTerm) {
return searchTerm ? MyCollection.find() : MyCollection.find({ $text: {$search: searchTerm} });
});
On the client, assuming you're using template-level subscriptions, you could set up your subscription like this:
Template.Subcategory.onCreated(function(){
var self = this;
// requires the reactive-var package
self.searchTerm = new ReactiveVar(false);
self.autorun(function(){
self.subscribe( "MyCollection", self.searchTerm.get() );
});
});
It'd then just be a case of setting your search term:
Template.Subcategory.events({
'keyup .searchbox': function(e,t){
var inputValue = e.currentTarget.value,
//you could set an arbitrary minimum search term length like so
searchTerm = inputValue.length > 1 ? inputValue : false;
t.searchTerm.set(searchTerm);
}
});
There's a couple of caveats on relying on the subscriptions so heavily like this. For example, if your collections are scoped globally on the client you run the risk of multiple subscriptions to the same collection giving you results you might not want to render within your list. Nevertheless, I quite like this approach. Food for thought.

MeteorJS ReactiveVar with database data

Clearly, I am doing something wrong with ReactiveVar because I cannot get it to work as I expect it should.
I am trying to set the value of an ReactiveVar by calling a Meteor.call method which returns the list of usernames. But it does not update when the usernames get changed in another part of the app.
I tried both:
Template.qastatistics.created = function () {
this.trackUsernames = new ReactiveVar(false);
var instance = Template.instance();
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
};
and:
Template.qastatistics.helpers({
users: function () {
var usernames,
instance = Template.instance();
if (instance.trackUsernames.get() === false) {
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
}
usernames = instance.trackUsernames.get();
...
But neither updates the list of usernames when these change in the database.
Is this even possible with ReactiveVars or have I completely misunderstood them?
EDIT: The usernames I mention are not from Meteor.users collection, but rather a distinct call from another collection that has usernames in it.
Fist of all I would use the onCreated function instead of defining created. That's a little more extendable and it's the new API. created is just kept around for backwards compatibility.
About your problem. You are right, you seem to have misunderstood what ReactiveVars do. They are a reactive data source. That means that when you call myReactiveVar.get in some Tracker.autorun (aka. reactive computation), the computation will rerun whenever myReactiveVar.set is called.
You got the first part right. Spacebars helpers always run inside their own computation. What you got wrong is thinking that a method call is a reactive action. That means, that you could call trackUsernames and set the trackUsernames ReativeVar again and the value in your template would update itself. But a method is only run once. It doesn't do anything fancy with reactivity.
A method call only transfers data once. When you publish a set of documents (like all users) on the other hand, they will be updated dynamically. Whenever a change happens inside that set of published documents, it will be synced to the client. So in general, it's a better idea to use publications and subscriptions to sync data reactively. If you'd want to use a method for the same thing you'd need to do some kind of polling (so your back in the stone-age again).
The easiest way to implement what you are trying to do is to use Meteor.users.find().fetch(). As it says in the docs fetch registers dependencies for all the documents you are fetching if it's being called from within a reactive computation.
First you'll need to properly set up your publications, so that users can see other users usernames. I'll leave that to you. Then you need to reimplement your helper
Template.qastatistics.helpers({
users: function () {
var usernames = _.pluck(Meteor.users.find().fetch(), 'username');
...
Thanks to suggestions from #kyll, I managed to get what I wanted by publishing the data I need:
server:
cope.publish.usernamesID = Random.id();
Meteor.publish("itemsusernames", function () {
self = this;
var initializing = true;
var handle = Items.find().observeChanges({
added: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
changed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
removed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
}
});
initializing = false;
self.added("itemsusernames", cope.publish.usernamesID, Items.distinct("p4User"));
self.ready();
self.onStop(function () {
handle.stop();
});
});
client:
users: function () {
var usernames = [],
oUsernames = ItemsUsernames.find().fetch();
if (!oUsernames[0]) return [];
usernames = $.map(oUsernames[0], function (value, index) {
if (!isNaN(index)) {
return [value];
}
});
...
And ofcourse: ItemsUsernames = new Mongo.Collection("itemsusernames");

Transforming on excluded field in meteor Mongo Collection

Meteor.publish("thing", function(options) {
return Collection.find({}, {fields: {anArray: 0}})
})
I exclude "anArray" because it contains userids not meant to be seen by each user. However it could contain the logged in user itself, in which case the user needs to know it.
Collection = new Mongo.Collection("thing", {
transform: function(document) {
_.each(document.anArray, function(item) {
item = true
})
return document
}
})
Above I try to transform the collection(simplified) but because the "anArray" is excluded, "anArray" is simply not defined.
How can I let the user know he is in "anArray" without compromising all other users in "anArray"? (I tried to do that in the transform.)
You can use the package I developed, meteor-middleware. It provides a nice pluggable API for this. So instead of just providing a transform, you can stack them one on another. This allows for code reuse, permissions checks (like removing or aggregating fields based on permissions), etc.
For example, for your particular problem, you could do (in CoffeeScript):
thing = new PublishEndpoint 'thing', (options) ->
Collection.find {}
class HideAnArrayMiddleware
added: (publish, collection, id, fields) =>
fields.anArray = _.intersection fields.anArray, [publish.userId] if fields.anArray
publish.added collection, id, fields
changed: (publish, collection, id, fields) =>
fields.anArray = _.intersection fields.anArray, [publish.userId] if fields.anArray
publish.changed collection, id, fields
thing.use new HideAnArrayMiddleware()
As described in this answer, here is how you access document fields before publishing them:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
var self = this;
var handle = Rooms.find({}).observeChanges({
added: function(id, fields) { self.added("rooms", id, fields); },
changed: function(id, fields) { self.changed("rooms", id, fields); },
removed: function(id) { self.added("rooms", id); },
}
});
self.ready();
self.onStop(function () { handle.stop(); });
});
In your case, maybe you can do something like this:
added: function(id, fields) {
if (fields.anArray)
if (fields.anArray.indexOf(self.userId) !== -1)
fields.anArray = [self.userId];
else
delete fields.anArray;
self.added("rooms", id, fields);
},
You'll also have to take care of the changed function in a similar way.
It's not possible to include or exclude elements of an array, so your best bet is to define an explicit Boolean field in the document for the user being in the array.
Also, because transforms on the server are ignored (please vote for this issue), you'll have to set that field in the database if it's dynamically computed. Similar SO questions: 1, 2, 3.
An alternative is to define a non-database-backed collection. Have a look at the counts-by-room example.

How to 'transform' data returned via a Meteor.publish?

Meteor Collections have a transform ability that allows behavior to be attached to the objects returned from mongo.
We want to have autopublish turned off so the client does not have access to the database collections, but we still want the transform functionality.
We are sending data to the client with a more explicit Meteor.publish/Meteor.subscribe or the RPC mechanism ( Meteor.call()/Meteor.methods() )
How can we have the Meteor client automatically apply a transform like it will when retrieving data directly with the Meteor.Collection methods?
While you can't directly use transforms, there is a way to transform the result of a database query before publishing it. This is what the "publish the current size of a collection" example describes here.
It took me a while to figure out a really simple application of that, so maybe my code will help you, too:
Meteor.publish("publicationsWithHTML", function (data) {
var self = this;
Publications
.find()
.forEach(function(entry) {
addSomeHTML(entry); // this function changes the content of entry
self.added("publications", entry._id, entry);
});
self.ready();
});
On the client you subscribe to this:
Meteor.subscribe("publicationsWithHTML");
But your model still need to create a collection (on both sides) that is called 'publications':
Publications = new Meteor.Collection('publications');
Mind you, this is not a very good example, as it doesn't maintain the reactivity. But I found the count example a bit confusing at first, so maybe you'll find it helpful.
(Meteor 0.7.0.1) - meteor does allow behavior to be attached to the objects returned via the pub/sub.
This is from a pull request I submitted to the meteor project.
Todos = new Meteor.Collection('todos', {
// transform allows behavior to be attached to the objects returned via the pub/sub communication.
transform : function(todo) {
todo.update = function(change) {
Meteor.call('Todos_update', this._id, change);
},
todo.remove = function() {
Meteor.call('Todos_remove', this._id);
}
return todo;
}
});
todosHandle = Meteor.subscribe('todos');
Any objects returned via the 'todos' topic will have the update() and the remove() function - which is exactly what I want: I now attach behavior to the returned data.
Try:
let transformTodo = (fields) => {
fields._pubType = 'todos';
return fields;
};
Meteor.publish('todos', function() {
let subHandle = Todos
.find()
.observeChanges({
added: (id, fields) => {
fields = transformTodo(fields);
this.added('todos', id, fields);
},
changed: (id, fields) => {
fields = transformTodo(fields);
this.changed('todos', id, fields);
},
removed: (id) => {
this.removed('todos', id);
}
});
this.ready();
this.onStop(() => {
subHandle.stop();
});
});
Currently, you can't apply transforms on the server to published collections. See this question for more details. That leaves you with either transforming the data on the client, or using a meteor method. In a method, you can have the server do whatever you want to the data.
In one of my projects, we perform our most expensive query (it joins several collections, denormalizes the documents, and trims unnecessary fields) via a method call. It isn't reactive, but it greatly simplifies our code because all of the transformation happens on the server.
To extend #Christian Fritz answer, with Reactive Solution using peerlibrary:reactive-publish
Meteor.publish("todos", function() {
const self = this;
return this.autorun(function(computation) {
// Loop over each document in collection
todo.find().forEach(function(entry) {
// Add function to transform / modify each document here
self.added("todos", entry._id, entry);
});
});
});

Can I store subscriptions in a collection named differently than on server

Is there a way to store subscriptions of the same server collection in a different minimongo collection?
If not is there any best practice to work around?
I do have a summary table having 50k datasets with a lot of details in the documents.
// Server
var collection = new Meteor.Collection("collection");
Meteor.publish("detail", function (id) {
return collection.find({_id: id});
});
// A pager that does not include the data (fields:{data:0})
Meteor.publish("master", function (filter, sort, skip, limit) {
return collection.find({name: new RegExp("^" + filter + "|\\s" + filter, "i")},
{limit: limit,
skip: skip,
sort: options,
fields: {data: 0}
});
});
// Client
var collection = new Meteor.Collection("collection");
Deps.autorun(function () {
Meteor.subscribe("master",
Session.get("search"),
Session.get("sort"),
Session.get("skip"),
Session.get("limit")
);
Meteor.subscribe("detail", Session.get("selection"));
});
Problem above: both subscriptions are feed into the same collection.
This does not work well if the results of the finds are stored in the same local collection.
Having a local collection with the name of the subscription/publish would be great.
// Client
var detail = new Meteor.Collection("detail"),
master = new Meteor.Collection("master");
Any Ideas how to encourage subscriptions to use my own collections??
I found the solution through help of Andrews Hint the Discover Meteor book that shows a lot of publishing subscription scenarios.
Anyway: After reading I discovered that the question I was heading for is also answered in the Meteor documentation Meteor.publish
The last example basically creates a virtual collection "counts" for the "messages" collection. Well done already ;-)
"Wer lesen kann ist im Vorteil!"

Resources