Finding documents in meteor.js - meteor

I'd like if someon could clarify this point about subscriptions in Meteor.js:
as written in the docs the find() function returns a cursor, not the data, that needs to be fetched;
let's say I have a publish function:
Meteor.publish('pages', function() {
return Pages.find()
});
now let's say I need only the page with code: "one"; in a template helper I can fetch that document:
Template.mytemplate.helpers({
data: function() {
return Pages.find({code: "one"});
}
});
The question is: is correct to do this, or if I need only one document would be better to subscribe only that document? (something like:
Meteor.publish('page', function(code) {
return Pages.find({code: code})
});
)
I mean: does it changes something in terms of efficiency/performance?

Yes, it does change a lot. If you subscribe to the entire collection, then the entire collection will be send to the client and kept in sync with the server. So by all means, if you only need one document, and do not need to cache any of the other documents (e.g., for switching to a new document), then only subscribe to the one you need.

Related

Why call collection.find in an Iron Router controller

I have built a small meteor app based on code generated by the excellent Meteor Kitchen project. This code works and renders the collection to the page, but there is one thing I am confused about.
A subset of the code is here:
router.js
this.route("articles", {path: "/articles", controller: "ArticlesController"});
ArticlesController
this.ArticlesController = RouteController.extend({
template: "Articles",
onBeforeAction: function() {
this.next();
},
action: function(){
if (this.isReady()) {
this.render();
} else {
this.render("loading");
}
},
isReady: function() {
var ready = true;
var subs = [ Meteor.subscribe('allArticles') ];
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
articles: Articles.find({})
};
}
});
server/pubs/articles.js
Meteor.publish('allArticles', function() {
return Articles.find({});
});
Meteor.publish('singleArticle', function(articleId) {
check(articleId, String);
return Articles.find({_id: articleId});
});
As I understand how this code is working, the following takes place:
(1) Collection is published via allArticles and singleArticle subscriptions
(2) ArticlesController subscribes to allArticles
(3) data function in the ArticlesController extracts the data (from the subscription?) to the articles array which is then exposed to the Blaze template.
Where I am confused:
Why do we need to do a Articles.find({}) in the data function? Where do we access the allArticles data ... it seems we are going back to the Articles collection directly and how is that possible if we have subscribed only to allArticles ?
While you don't show it, I'm assuming you have the following line in your code, defined somewhere that will execute it on both the server, and the client:
Articles = new Mongo.Collection('articles');
/*CollectionName = new Mongo.Collection('DBCollectionName');*/
Docs. When this is executed on the server a Collection is created, and assigned to the variable name Articles. 'articles' is the name used to store this collection in MongoDB.
When this is executed on the client, a mini mongo Collection is created. It initially will have no documents in it.
Next you have server only code:
Meteor.publish('allArticles', function() {
return Articles.find({});
});
Meteor.publish('singleArticle', function(articleId) {
check(articleId, String);
return Articles.find({_id: articleId});
});
Docs. This defines two publications, 'allArticles' and 'singleArticle'. These are not Collections themselves, but are rules that specify a set of data that the server will publish, and a client may subscribe to. While these two publications return data from the Server's Articles collection, publications can return data from one or more collections, or by directly using the underlying ddp protocol you can publish data that comes from another data source (not mongodb).
Next on the client you subscribe to a collection:
Meteor.subscribe('allArticles')
Docs. This call takes the name of a publication defined on the server ('allArticles'), and subscribes to it. The server then executes the publish function, and sends over ddp the set of data returned. This data is stored in the Client-side Mini Mongo Collection created above, and named Articles.
Also the server will monitor the Articles collection for changes, and if the resultset of the 'allArticles' publication changes, will send these changes as updates to the client.
So next you have the data function in your Controller (Client side).
data: function() {
return {
articles: Articles.find({})
};
}
Docs. This sets the data context for the render function.
The reason this calls Articles.find rather than allArticles.find is because allArticles is not a collection, but was instead the name of the publication the client used to request the server send data, that was stored in the clients mini mongo collection named Articles.
Where do we access the allArticles data ... it seems we are going back
to the Articles collection directly and how is that possible if we
have subscribed only to allArticles ?
You return this as part of your data object so that you can access it in your template. In your Articles template you can now use {{#each articles}} directly without a helper function because articles is part of your data context. You can also access the articles returned from your controllers data portion inside of your Articles template helpers by using this.articles.
Why do we need to do a Articles.find({}) in the data function?
These queries being performed in your controllers data function act on the clients minimongo Articles collection as opposed to the servers. Once the information is published from the server, and the client has subscribed to it, the client has this information available in their minimongo instance, but still needs to access it somehow. Basically, the publication makes the information available, but the Articles.find({}) accesses it for the client.
Accessing this information inside of the data function of your controller is simply to avoid doing it inside of your template.
I think that your misunderstanding comes from the third step that you have described:
data function in the ArticlesController extracts the data (from the
subscription?) to the articles array which is then exposed to the
Blaze template.
The data function extracts the data from minimongo on the client which contains the information from the subscription. Minimongo lies between the subscription and the data function.
I would need to know more about your app to answer the question. Are you viewing just a single article, or is there a page that lists them all?
If you are viewing a single article you would need to subscribe to the singleArticle publication.
If you were showing a list of articles, you would need to subscribe to allArticles. If there are a lot of articles, you could improve the speed of your app by limiting the number of fields with a query projection.

Meteor: Publish a subset of another publication

I have a custom publication on my server (which in some way join 2 collections).
This resulting set of this publication is exactly what I need but for performances issues I would like to avoid sending it entirely to the client.
If I did not care about performances, I would only subscribe to the
publication and do something like
theCollection.find({"my":"filter"})
I am therefore trying to find a way to publish a subset of the custom publication so that the filter would be applied on the custom publication on the server side.
Is there a way to chain or filter publications (server side) ?
For the question we can assume the custom publication to look like this and cannot be modified:
Meteor.publish('customPublication', function() {
var sub = this;
var aCursor = Resources.find({type: 'someFilter'});
Mongo.Collection._publishCursor(aCursor, sub, 'customPublication');
sub.ready();
});
if i understand the question right, you are looking for https://atmospherejs.com/reywood/publish-composite
It let's you "publish a set of related documents from various collections using a reactive join. This makes it easy to publish a whole tree of documents at once. The published collections are reactive and will update when additions/changes/deletions are made."
Ok I came to the following workaround. Instead of working on the publication, I simply added a new collection I update according to the other collections. In order to do so I am using the meteor hooks package
function transformDocument(doc)
{
doc.aField = "aValue"; // do what you want here
return doc;
}
ACollection.after.insert(function(userId, doc)
{
var transformedDocument = transformDocument(doc);
AnotherCollection.insert(transformedDocument);
});
ACollection.after.update(function(userId, doc, fieldNames, modifier, options)
{
var transformedDocument = transformDocument(doc);
delete transformedDocument._id;
AnotherCollection.update(doc._id,{$set:transformedDocument});
});
ACollection.after.remove(function(userId, doc)
{
AnotherCollection.remove(doc._id);
});
Then I have the new collection I can publish subsets the regular way
Benefits:
You can filter whatever you want into this db, no need to worry if the field is virtual or real
Only one operation every time a db changes. This avoid having several publication merging the same data
Cave eats:
This requires one more Collection = more space
The 2 db might not be always synchronised, there is few reasons for this:
The client manually changed the data of "AnotherCollection"
You had documents in "ACollection" before you added "AnotherCollection".
The transform function or source collection schema changed at some point
To fix this:
AnotherCollection.allow({
insert: function () {
return Meteor.isServer;
},
update: function () {
return Meteor.isServer;
},
remove: function () {
return Meteor.isServer;
}
});
And to synchronise at meteor startup (i.e. build the collection from scratch). Do this only once for maintenance or after adding this new collection.
Meteor.startup(function()
{
AnotherCollection.remove({});
var documents = ACollection.find({}).fetch();
_.each(documents, function(doc)
{
var transformedDocument = transformDocument(doc);
AnotherCollection.insert(transformedDocument);
});
});

Meteor: Get count of collection by name. Accessing global scope on server

I'd like to create a method that returns the count of a generic collection.
Calling the method would look something like this:
Meteor.call('getCollectionCount', 'COLLECTION_NAME');
And the result would be the collection count.
The server method code would look something like this:
getCollectionCount: function (collectionName) {
return window[collectionName].find().count();
}
This won't work because window isn't defined on the server, but is something similar possible?
Use global instead of window.
Note that this uses the variable name assigned to the collection object, not the name given to the collection. For this to work with Meteor.users you need to assign another variable name.
if (Meteor.isServer) {
users = Meteor.users;
}
if (Meteor.isClient) {
Meteor.call('count', 'users', function (err, res) {
// do something with number of users
});
}
Also probably a good idea to check that global[collectionName] is actually a collection.
I came up with this code which makes the following assumptions :
collections are declared in the global scope as top level objects.
collections are searched by collection name, not the collection variable identifier.
So client code should declare their collections like this :
MyCollection=new Meteor.Collection("my-collection");
And use the function like this :
var clientResult=Meteor.call("getCollectionCount","my-collection",function(error,result){
if(error){
console.log(error);
return;
}
console.log("actual server-side count is : ",result);
});
console.log("published subset count is : ",clientResult);
The method supports execution on the client (this is known as method stub or method simulation) but will only yield the count of the collection subset replicated client-side, to get the real count wait for server-side response using a callback.
/packages/my-package/lib/my-package.js
getCollection=function(collectionName){
if(collectionName=="users"){
return Meteor.users;
}
var globalScope=Meteor.isClient?window:global;
for(var property in globalScope){
var object=globalScope[property];
if(object instanceof Meteor.Collection && object._name==collectionName){
return object;
}
}
throw Meteor.Error(500,"No collection named "+collectionName);
};
Meteor.methods({
getCollectionCount:function(collectionName){
return getCollection(collectionName).find().count();
}
});
As Meteor.users is not declared as a top level variable you have to account for the special case (yes, this is ugly).
Digging into Meteor's collection handling code could provide a better alternative (getting access to a collection handle by collection name).
Final words on this : using a method call to count a collection documents is unfortunately non-reactive, so given the Meteor paradigm this might be of little use.
Most of the time you will want to fetch the number of documents in a collection for pagination purpose (something like a "Load more" button in a posts list for example), and as the rest of the Meteor architecture you'll want this to be reactive.
To count documents in a collection reactively you'll have to setup a slightly more complicated publication as showcased in the "counts-by-room" example in the docs.
http://docs.meteor.com/#meteor_publish
This is something you definitely want to read and understand.
This smart package is actually doing it right :
http://atmospherejs.com/package/publish-counts
It provides a helper function that is publishing the counts of any cursor.
Keep track of the collections on some other property that the server has access too. You could even call it window if you really wanted to.
var wow = new Meteor.Collection("wow");
collections["wow"] = wow;
getCollectionCount: function (collectionName) {
return collections[collectionName].find().count();
}
If you don't want the package users to change how they work with collections in the app then I think you should use MongoInternals to get collections by name from the db. Not tested but here is an example:
//on server
Meteor.methods({
count: function( name ){
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var collection = db.collection( name );
return collection && collection.count({});
}
});
Another example of MongoInternals use is here. Documentation of the count() function available from the mongo driver is here.

Extend a meteor collection server and client side

I am currently developping an app with the amazing Meteor platform. I would like to do something with my collections but I couldn't really find how to do it from the examples I have seen so far.
Basically I would like to display a list of items which contains their own countdown. Each items core data come from a collection. Each countdown starting times must be computed server side and not saved anywhere. Each countdown are computed client side and not saved anywhere.
I have a collection named "items" coming from my MongoDb db. At the beginning document in my collections could look like:
{ name: "My countdown"}
1) I would like to "extend" the documents server side in adding a computed property "startTime". A documents could look like then:
{ name: "My countdown", startTime: 40 }
I guess I need to use the publish method, but I don't really get how to extend existing documents that way.
2) I would like to "extend" the documents client side in adding a local property "currentTime", that i will update with a setInterval. A document could look like then:
{ name: "My countdown", startTime: 40, currentTime: 5 }
Maybe using a transform there but once again I don't really get how to extend existing documents.
3) I would likethoses 2 new properties reactives and so trigger some updates in the UI if they change.
So if i could get any starting points and good pratices it will be really appreciated :)
Many thanks for your help!!
You can update a document of a Collection: Best practice is to do this on the server.
client.js
Meteor.call('setStartTime',
[your_document_id],
[new_start_time],
function(err, val) {
if (err) {
console.error(err);
} else {
// Successful.
}
});
server.js
Meteor.methods({
'setStartTime': function(itemId, newStartTime) {
Items.update(itemId, {
$set: { startTime: newStartTime }
});
}
});
This will set or update the startTime of your item. (Be cautious, as anyone with access to your JavaScript will be able to see your setStartTime call on the client. This is functional, but not secure.)

Confused about Publish/Subscribe and returning results

I am using meteor and i am a bit confused about the relationship between publishing/subscribing to documents and querying/returning collections to a client using the handlebars #each items helper.
I understand that by publishing and subscribing to certain documents, i get the reactive updating on the client side browser when things change.
I find my self writing very complex (role oriented) publish functions and writing the equivalent to return items to the client. For example,
Meteor.publish("directory", function () {
var user = Meteor.users.findOne({_id:this.userId});
//role and logic left out on purpose
return Meteor.users.find({}, {fields:{emails:1, profile:1}});
});
and the subscribe
if (Meteor.userId() != null) {
Meteor.subscribe("directory");
}
Template is called show people and the helper 'users'
Template.show_people.users = function () {
users = Meteor.users.find({}).fetch();
return users;
};
My question is, are things supposed to be done this way?. Do we return our list helpers the same query we used for publish?
You can give a query cursor to the #each Handlebars function. In fact, it's recommended. In this manner, there will be a smart update of the DOM: when a document is added to the Cursor, Handlebars will only create new DOM nodes for that document, and not recreate the DOM nodes for the documents that were already present. This is not the case when you provide an array.
So that third piece of code can just be:
Template.show_people.users = function () {
return Meteor.users.find({});
};
Note also that a collection.find() done client-side will only look in the documents inside your miniMongo storage... you're not doing a search through the entire server database, but only through the documents that the server has published to you.
So that complex, role-oriented logic is only necessary inside your Meteor.publish() function.

Resources