How to update a doc value from an iron-router function - mongodb

I know this is a nood question, but I'm trying to work out how to update a value in a document from a route in iron router. I've found the spot I need to put the function, but I'm struggling with the mongo code needed to make it work.
I'm trying to increment a views element each time a link is clicked, so have added the following code to the route.
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log(project.views);
projectDocs.update({id: project.id},
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
The project.views value is returning the correct value, but the code to update the value throws an exception at the moment.
I tried the simple thing of project.views++ which increments the variable within the function but it never gets pushed to the database (no surprises there I guess).
Can someone point me in the direction I need to get this value to inc (and is this even the right place to do this?).
Thanks.
Peter.
OK, I found this link that has lead me part of the way http://books.google.com.au/books?id=uGUKiNkKRJ0C&pg=PA37&lpg=PA37&dq=Cannot+apply+$inc+modifier+to+non-number&source=bl&ots=h7qyOddRsf&sig=EWFw9kNLGHoFEUS-nTNsBStDRcQ&hl=en&sa=X&ei=cRGXUse0DNGciAfk6YHgCA&ved=0CFcQ6AEwBQ#v=onepage&q=Cannot%20apply%20%24inc%20modifier%20to%20non-number&f=false which explains that you can only inc numeric values (I had this as a string it seems.
Now the problem is that I seem to be in an endless loop.
The function now looks like
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log("Views", project.views);
console.log("Project", project);
projectDocs.update(project._id,
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
Why would this be looping?

Use _id instead of id. So
projectDocs.update({_id: project._id},
{$inc: {views: 1}}
);
If that's not it, perhaps you could update your answer with whatever exception you are getting.

Just read the fantastic new documentation on iron-router a bit further and moved the $inc function to the unload hook and all seems to be good.
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
return projectDocs.findOne(this.params._id);
},
unload: function() {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
projectDocs.update(project._id,
{$inc: {views: 1}}
);
}
// could possibly use layout: popup_layout? here
});
Would love some confirmation that this is actually where I should be doing this (and it does seem a bit inefficient to be doing so many "findOne"'s) but its working for the moment.

Related

Meteor: Increment DB value server side when client views page

I'm trying to do something seemingly simple, update a views counter in MongoDB every time the value is fetched.
For example I've tried it with this method.
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
if (Meteor.isServer) {
var message = Messages.findOne(
{_id: messageId}
);
var views = message.views;
// Increment views value
Messages.update(
messageId,
{ $set: { views: views++ }}
);
}
return Messages.findOne(
{_id: messageId}
);
},
});
But I can't get it to work the way I intend. For example the if(Meteor.isServer) code is useless because it's not actually executed on the server.
Also the value doesn't seem to be available after findOne is called, so it's likely async but findOne has no callback feature.
I don't want clients to control this part, which is why I'm trying to do it server side, but it needs to execute everytime the client fetches the value. Which sounds hard since the client has subscribed to the data already.
Edit: This is the updated method after reading the answers here.
'messages.get'(messageId) {
check(messageId, String);
Messages.update(
messageId,
{ $inc: { views: 1 }}
);
return Messages.findOne(
{_id: messageId}
);
},
For example the if(Meteor.isServer) code is useless because it's not
actually executed on the server.
Meteor methods are always executed on the server. You can call them from the client (with callback) but the execution happens server side.
Also the value doesn't seem to be available after findOne is called,
so it's likely async but findOne has no callback feature.
You don't need to call it twice. See the code below:
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
var message = Messages.findOne({_id:messageId});
if (message) {
// Increment views value on current doc
message.views++;
// Update by current doc
Messages.update(messageId,{ $set: { views: message.views }});
}
// return current doc or null if not found
return message;
},
});
You can call that by your client like:
Meteor.call('messages.get', 'myMessageId01234', function(err, res) {
if (err || !res) {
// handle err, if res is empty, there is no message found
}
console.log(res); // your message
});
Two additions here:
You may split messages and views into separate collections for sake of scalability and encapsulation of data. If your publication method does not restrict to public fields, then the client, who asks for messages also receives the view count. This may work for now but may violate on a larger scale some (future upcoming) access rules.
views++ means:
Use the current value of views, i.e. build the modifier with the current (unmodified) value.
Increment the value of views, which is no longer useful in your case because you do not use that variable for anything else.
Avoid these increment operator if you are not clear how they exactly work.
Why not just using a mongo $inc operator that could avoid having to retrieve the previous value?

MongoDB - Update field with reference of another document

I would like if is possible to update a field of all documents in a collection with a reference to another document. I have tried to do this with the code below:
var project = db.Project.find({slug:"engine"});
db.Activity.update({}, {$set:{'project':DBRef("Project", project._id, "mydb")}});
When I look at the Activity documents, in the "project" field, the result is:
{
_id: ObjectId("..."),
"project": DBRef("Project", undefined, "mydb")
}
Is there a way to do this correctly?
Thanks in advance.
Seems to me you're having a promise callback problem. You can solve it in two ways:
Option one: Put the function depending of your data return inside a callback of the first function, for example:
db.Project.find({slug:"engine"}, function(error, data) {
db.activity.update(...data.Id...);
});
Option two: Wait for the return of the find to be completed:
var project = db.Project.find({slug:"engine"});
project.then(function(error,data) {
db.activity.update(...project.Id...);
});
Both should work. The problem is that when you make the first call, it returns a promise, not the value itself. If you are making confusion on this topic, you can take a look at:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Hope my answer helped you.

Meteor observeChanges(). How to check the actual changes?

I have a code that looks like this
Trades.find().observeChanges({
changed: function(id, fields) {
// do stuff
}
});
Where each Trades has an array of items inside
TradesSchema = new SimpleSchema({
// ...
items: {
type: [String]
},
// ...
});
Trades.attachSchema(TradesSchema);
These items are being changed sometimes, and I want to track the changes. The code works fine, except that in fields it returns all the items, not only the items that were changed.
Is there any way to see which exactly item was changed without changing the structure of the collection?
Thanks #Season for the hint!
observeChanges only gives the new values, so you have to use observe, since it returns both the new and old documents. Then you need to compare them to see what exactly got changed. (See docs for observe on meteor.com)
Trades.find().observe({
changed: function(newDocument, oldDocument) {
// compare newDocument to oldDocument to find out what has changed
}
});

Meteor methods and Mongo $inc non-number error

I'm going through the methods chapter of Your First Meteor Application by David Turnbull.
I have a method for updating a field in the database.
'modifyPlayerScore': function(selectedPlayer, scoreValue){
PlayersList.update(selectedPlayer, {$inc: {score: scoreValue} });
}
and these methods are being called from event functions
'click .increment': function(){
var selectedPlayer = Session.get('selectedPlayer');
Meteor.call('modifyPlayerScore', selectedPlayer, 5);
},
'click .decrement': function(){
var selectedPlayer = Session.get('selectedPlayer');
Meteor.call('modifyPlayerScore', selectedPlayer, -5);
}
When I use this functions in the app, I see an error in Terminal
Exception while invoking method 'modifyPlayerScore' MongoError: Modifier $inc allowed for numbers only
I have used a console.log statement to print the scoreValue variable and it shows either 5 or -5. I have a feeling that this may be a string and not a number but I'm not sure how to fix this error. Thanks in advance for your help!
When you added the score to a player with :
PlayersList.insert({name: 'test', score:3});
I suppose, you could increase the score. But not anymore.
It's because you passed a text parameter instead of an integer.
When you add a player you should use parseInt():
PlayersList.insert({
name: name,
score: parseInt(score),
createdBy: Meteor.userId()
})
Now, it should work. Or you can use parseInt() to set score
You should change the Meteor.method to this.
On the $inc remove the 5 static and place the second argument (scoreValue).
The method should look like this.
modifyPlayerScore': function(selectedPlayer, scoreValue){
PlayersList.update(selectedPlayer, {$inc: {score: scoreValue} });
}
And now you can make the call like this.
Meteor.call('modifyPlayerScore', selectedPlayer, 5);
where 5 its now the scoreValue argument
UPDATE
I made this working MeteorPad check you have everything like this.
NEW METEORPAD
I made this meteor pad based on the gist, and everything its working.
I used parse int on the score within PlayerList.insert as suggested above by yoh and it works for new entries. The old entries for score are still saved as strings so increment and decrement do not work. Delete your old entries and start fresh, it should work.

How to call cascading find() in Meteor + MongoDB?

I use Meteor & Iron-router. I have the following data context defined in the router:
data: function() { return {
eventId: this.params._id,
registrants: Registrants.find({eventIds: {$elemMatch: { $in: [this.params._id]}}}, {sort: {name:1, phone:1, email:1}}),
}}
I want to enable Registrants to be filtered further by user input. In my case, I already have ReactiveVar called filterName which listen to input text from user. Whenever the input text changed, the filterName is updated. ( I followed this answer ng-repeat + filter like feature in Meteor Blaze/Spacebars)
Now, I want to add $and to the Registrants.find() method to derive new registrants data context. How should I do it so that the query is reactive to the filterName?
Another approach is by defining Template helper method filteredRegistrants. Initially, its value is the same as return this.registrants. Whenever filterName changed, I would do return this.registrants.find({name: filterName}), but somehow I can't invoke find from registrants cursor, can I? I got undefined is not function error when doing that.
this.registrants is already a cursor (result of Registrants.find()), and not a collection, thus it doesn't have the find() method you look for. However, there is nothing wrong with making another query in the helper if the functionality provided by your controller is not enough:
Template.registrantsTemplate.helpers({
filteredRegistrants: function() {
return Registrants.find(...query...);
},
});