change collection before publishing - meteor

I would like to add a property to the objects that get published to the client.
My publish function looks like that
Meteor.publish("forms", function() {
return Forms.find();
});
I would like to do something like this
Meteor.publish("forms", function() {
var forms = Forms.find();
forms.forEach(function (form) {
form.nbForms = 12;
}
return forms;
});
What I would like is that all the documents in forms have a new count attribute which gets sent to the client.
But this obviously does not work.
thank you for your help

Not sure it will work in your case but you might use the new transform collection function introduced with Meteor 0.5.8
When declaring your collection, add this function as the second parameter :
Forms = new Meteor.Collection("forms", {
transform: function(f) {
f.nbForms = 12;
return f;
}
});
But this will be on both server and client. I don't know if there is a way to define a transform function in a publish context.

I think you need to do something similar to this Meteor counting example in Publish:
How does the messages-count example in Meteor docs work?
I also posted a question here that may help once it's answered. Meteor has a this.added which may work, but I'm currently uncertain how to use it. Hence the question below:
Meteor, One to Many Relationship & add field only to client side collection in Publish?

Related

Meteor Dashboard and publication/subscription

EDIT: as the original question was too vague I have updated it to make it more concrete
I'd like to create a dashboard in Meteor that shows some statistics about my collections (e.g. how many docs, how many users...). I have been trying the past days but can't seem to find a good/intelligent way.
I initially just did the following:
Template.dashboard.helpers({
getProductsCount: function() {
return Products.find().count();
}
});
This did not work. I think because it counts the number of products from minimongo, but not sure.
Then I did the following:
In the template helper, call a method and get the value to show on the dashboard page (does not work)
Was told not to use pub/sub mechanism for this type of metric
Worked via Session variables (did work, but feels a bit strange to store this kind of metric data in Session variables
So then I read in another SO response about Reactive Variables and tried the following:
Template.dashboard.helpers({
getProductsCount: function() {
return Template.instance().myAsyncValue.get();
}
});
Template.dashboard.created = function() {
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from server");
Meteor.call('getProductsCount', function(error, asyncValue){
if (error)
console.log(error);
else
self.myAsyncValue.set(asyncValue);
});
};
This works, but I find this extremely difficult for something as simple as showing a product count (or any other metric). Not sure I understand the reason why I should use sth as reactive variables?
Then -out of curiosity- I tried the following (using meteor add simple:reactive-method) and it works:
Template.customerDashboard.helpers({
getProductsCount: function () {
return ReactiveMethod.call("getProductsCount");
}
});
So the question really is why having to use Reactive variables and methods for sth as simple as this. Can someone explain?
If you want to show the count only in the view, the best way is to return the count number only. you do not need publish/subscribe at all. you can use server methods. and if you want to show data also, you can go for pub-sub. and your approach is correct.

Finding documents in meteor.js

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.

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.)

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

How to work with async code in Mongoose virtual properties?

I'm trying to work with associating documents in different collections (not embedded documents) and while there is an issue for that in Mongooose, I'm trying to work around it now by lazy loading the associated document with a virtual property as documented on the Mongoose website.
The problem is that the getter for a virtual takes a function as an argument and uses the return value for the virtual property. This is great when the virtual doesn't require any async calls to calculate it's value, but doesn't work when I need to make an async call to load the other document. Here's the sample code I'm working with:
TransactionSchema.virtual('notebook')
.get( function() { // <-- the return value of this function is used as the property value
Notebook.findById(this.notebookId, function(err, notebook) {
return notebook; // I can't use this value, since the outer function returns before we get to this code
})
// undefined is returned here as the properties value
});
This doesn't work since the function returns before the async call is finished. Is there a way I could use a flow control library to make this work, or could I modify the first function so that I pass the findById call to the getter instead of an anonymous function?
You can define a virtual method, for which you can define a callback.
Using your example:
TransactionSchema.method('getNotebook', function(cb) {
Notebook.findById(this.notebookId, function(err, notebook) {
cb(notebook);
})
});
And while the sole commenter appears to be one of those pedantic types, you also should not be afraid of embedding documents. Its one of mongos strong points from what I understand.
One uses the above code like so:
instance.getNotebook(function(nootebook){
// hey man, I have my notebook and stuff
});
While this addresses the broader problem rather than the specific question, I still thought it was worth submitting:
You can easily load an associated document from another collection (having a nearly identical result as defining a virtual) by using Mongoose's query populate function. Using the above example, this requires specifying the ref of the ObjectID in the Transaction schema (to point to the Notebook collection), then calling populate(NotebookId) while constructing the query. The linked Mongoose documentation addresses this pretty thoroughly.
I'm not familiar with Mongoose's history, but I'm guessing populate did not exist when these earlier answers were submitted.
Josh's approach works great for single document look-ups, but my situation was a little more complex. I needed to do a look-up on a nested property for an entire array of objects. For example, my model looked more like this:
var TransactionSchema = new Schema({
...
, notebooks: {type: [Notebook]}
});
var NotebookSchema = new Schema({
...
, authorName: String // this should not necessarily persist to db because it may get stale
, authorId: String
});
var AuthorSchema = new Schema({
firstName: String
, lastName: String
});
Then, in my application code (I'm using Express), when I get a Transaction, I want all of the notebooks with author last name's:
...
TransactionSchema.findById(someTransactionId, function(err, trans) {
...
if (trans) {
var authorIds = trans.notebooks.map(function(tx) {
return notebook.authorId;
});
Author.find({_id: {$in: authorIds}, [], function(err2, authors) {
for (var a in authors) {
for (var n in trans.notebooks {
if (authors[a].id == trans.notebooks[n].authorId) {
trans.notebooks[n].authorLastName = authors[a].lastName;
break;
}
}
}
...
});
This seems wildly inefficient and hacky, but I could not figure out another way to accomplish this. Lastly, I am new to node.js, mongoose, and stackoverflow so forgive me if this is not the most appropriate place to extend this discussion. It's just that Josh's solution was the most helpful in my eventual "solution."
As this is an old question, I figured it might use an update.
To achieve asynchronous virtual fields, you can use mongoose-fill, as stated in mongoose's github issue: https://github.com/Automattic/mongoose/issues/1894

Resources