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.
Related
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.
What could be wrong with this update function?
fixrecs2 = function() {
var arr = myColl.find({ d: 1 }).fetch();
for (var i = 0; i < arr.length; i++) {
var code = arr[i].c;
var rec = myOtherColl.findOne( { cc: code });
if (rec) {
c(rec._id)
myOtherColl.update( {_id: rec._ID }, { $set: {dt: "ant"} } );
}
}
console.log(i + " records processed.");
}
I have never had trouble updating my documents before in this way. Checking the output in the console, I can tell that all the records that I expect to find are there. I can see their _id values printed by console.log(). But the dt field does not get updated. In some cases, the dt field already exists, in some cases it doesn't, but update is supposed to add a field if it's not there, right?
I have tried adding a callback, but it did not seem to run. (I have not been able to find a good callback example for the update function.) In any case, according to the docs, I should get an error message in the console if update fails. I'm still running the insecure package, so there's no allow or deny rules to worry about. I'm really stumped by this!
The issue in this case, turned out to be a simple misspelling, rec._ID is not the same as rec._id. This was likely overlooked due to the common capitalization of MongoDB's ObjectID.
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...);
},
});
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.
I am following the recently published book "Getting Started with Meteor.js JavaScript Framework" by Isaac Strack. The book works with Meteor 0.5.0. I am working with version 0.5.4.
In the book you build an app with a few categories to which you insert data for tracking household items, and who they may be lent to. I deployed the app to a meteor subdomain, and it is working perfectly. It does not replicate my local MongoDB error.
I am in Chapter 5, and I have just removed autopublish from the app, and specified my local channels for data.
Locally, under the under the "Tools" category only, when I try to add a new item to the category, I recieve this error in my browser console:
Exception while simulating the effect of invoking '/Lists/update' Error {} Error: Cannot apply $addToSet modifier to non-array
at Error (<anonymous>)
at LocalCollection._modifiers.$addToSet (http://localhost:3000/packages/minimongo/modify.js?e7f02f0df0bff9f0b97236f9548637b7ede1ac74:178:13)
at Function.LocalCollection._modify (http://localhost:3000/packages/minimongo/modify.js?e7f02f0df0bff9f0b97236f9548637b7ede1ac74:53:9)
at LocalCollection._modifyAndNotify (http://localhost:3000/packages/minimongo/minimongo.js?7f5131f0f3d86c8269a6e6db0e2467e28eff6422:474:19)
at LocalCollection.update (http://localhost:3000/packages/minimongo/minimongo.js?7f5131f0f3d86c8269a6e6db0e2467e28eff6422:444:12)
at m.(anonymous function) (http://localhost:3000/packages/mongo-livedata/collection.js?3ef9efcb8726ddf54f58384b2d8f226aaec8fd53:415:36)
at http://localhost:3000/packages/livedata/livedata_connection.js?367884963b120d457819216ff713b2586b266dde:540:25
at _.extend.withValue (http://localhost:3000/packages/meteor/dynamics_browser.js?46b8d1f1158040fcc2beb7906ec2f932871a398d:21:19)
at _.extend.apply (http://localhost:3000/packages/livedata/livedata_connection.js?367884963b120d457819216ff713b2586b266dde:539:47)
at Meteor.Collection.(anonymous function) [as update] (http://localhost:3000/packages/mongo-livedata/collection.js?3ef9efcb8726ddf54f58384b2d8f226aaec8fd53:266:23) logging.js:30
update failed: Internal server error logging.js:30
The tools category already has one item in it which was submitted earlier in the tutorial. If I type into the console lists.findOne({Category:"Tools"}); I get the output which recognizes an item in the Object:
Object
Category: "Tools"
_id: "eaa681e1-83f2-49f2-a42b-c6d84e526270"
items: Object
LentTo: "Steve"
Name: "Linear Compression Wrench"
Owner: "me"
__proto__: Object
__proto__: Object
However, the screen output is blank:
Naturally I have tried restarting the meteor server & shut down the browser, but no resolution. I am new to MongoDB, so I am unclear as to where to turn to understand what is causing this problem, or why.
You can view the app here. You can view the code on my GitHub.
function addItem(list_id, item_name) {
if(!item_name && !list_id)
return;
lists.update({_id:list_id}, {$addToSet:{items:{Name:item_name}}});
}
Seems like you're trying to add an object to a set. You're getting an error on simulation. Let's investigate that error. The code that errors out:
https://github.com/meteor/meteor/blob/master/packages/minimongo/modify.js
$addToSet: function (target, field, arg) {
var x = target[field];
if (x === undefined)
target[field] = [arg];
else if (!(x instanceof Array))
throw Error("Cannot apply $addToSet modifier to non-array");
else { ...
Uh oh, throw Error("Cannot apply $addToSet modifier to non-array.").
Look at your code:
Object
Category: "Tools"
_id: "eaa681e1-83f2-49f2-a42b-c6d84e526270"
...
items: Object
...
items is an object, not an array! It will error out.
Can you $addToSet to an object with Mongo? Let's look at the code.
https://github.com/mongodb/mongo/blob/4a4f9b1d6dc79d1ba4a7d7eaa9e4eb6d00aa466c/db/update.cpp
case ADDTOSET: {
uassert( 12592 , "$addToSet can only be applied to an array" , in.type() == Array );
...
}
Nope! This is from old Mongo code, because the contemporary codebase is sprawling, but same thing.
I only found one insert in your code.
'keyup #add-category': function(e, t) {
if (e.which === 13) {
var catVal = String(e.target.value || "");
if (catVal) {
lists.insert({Category:catVal});
Session.set('adding_category', false);
}
}
},
Try lists.insert({Category:catVal,items:[]}). So that items is initialized as an array rather than an object when it was first used.
Also, I don't think $addToSet compares objects in an array the way you would like anyway, so consider making a separate collection Items that contains a categoryId.
It is purely a coincidence that it is working on one place and not another.
I had the same problem following the same guide. When using the inserts directly from the guide (the Tools and DVDs) minimongo inserts the items as objects instead of arrays. I don't know if it was simply a mistake or if the code base has changed, either way I changed:
lists.insert({Category:"DVDs", items: {Name:"Mission Impossible", Owner:"me", LentTo:"Alice"}});
to
lists.insert({Category:"DVDs", items: [{Name:"Mission Impossible", Owner:"me",LentTo:"Alice"}]});
just encasing the items in [] to force it to an array and did likewise with the insert for tools. Works great now.