Why on earth is this update failing? This is basic, I've done this many other times.
Meteor.users.update(id, {$set: {lastname: "Archer"}});
That query above has even been simplified from what it originally was, and it's still having the same problem. Originally I was compiling the update programatically:
console.log(obj);
console.log(id);
if (obj) collection.update(id, obj);
Here is my exact output from those lines:
Object {$set: Object}
$set: Object
lastname: "Archer"
__proto__: Object
__proto__: Object
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:68
YXeudfnHyKmsGXaEL
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:69
update failed: MongoError: invalid query
debug.js:41
The id is correct, and if the obj has something wrong with it, it'll be news to me!
Now, I'm quite positive this has nothing to do with my allow function. In the first tests of this part of my program, I actually did get the update not allowed (or whatever it is) error, but I modified my allow function to take this into account, and that error went away.
Here's my allow function. I have a user system where a user can have a student object that "mirrors" it's name information, hence the added complexity. That portion of the function is only engaged in certain circumstances, and it doesn't alter the behavior of the allowance.
Meteor.users.allow({
update: function (userId, doc, fields, modifier) {
var allow = (userId && (doc._id === userId) && _.without(fields,
'firstname', 'lastname', 'student_ids', 'payment_ids', 'phones').length == 0) || Meteor.user().admin;
if (allow && modifier.$set && (_.contains(fields, 'firstname') || _.contains(fields, 'lastname'))) {
var user = Meteor.users.findOne(userId);
var obj = {};
if (modifier.$set.firstname) obj.firstname = modifier.$set.firstname;
if (modifier.$set.lastname) obj.lastname = modifier.$set.lastname;
if (obj) Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
}
return allow;
}
});
It turns out my allow function was the problem, but in a bit of a sneaky way.
The Meteor.users.update call wasn't actually the one that was failing, it was this one:
Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
I wasn't properly checking that user.student_ids field, so if it was undefined (that user didn't have any students) then the query was invalid. Throwing in a line to vet that array:
var student_ids = user.student_ids || [];
solved the problem.
Since the Meteor error didn't tell me which query was invalid, it led me on a bit of a wild goose chase. Just goes to show that good errors can go a long way!
Also, performing database queries that have other database queries as side-effects is something to be done very carefully.
Related
I am trying to handle errors using findOne in meteor-mongo.
From this stackoverflow question, it appears that I should be able to handle errors by doing collection.findOne({query}, function(err, result){ <handleError> }, but doing so results in an errormessage:
"Match error: Failed Match.OneOf, Match.Maybe or Match.Optional validation"
The following code works:
export default createContainer((props) => {
let theID = props.params.theID;
Meteor.subscribe('thePubSub');
return {
x: theData.findOne({_id: theID}),
};
}, App);
The following code does not:
export default createContainer((props) => {
let theID = props.params.theID;
Meteor.subscribe('thePubSub');
return {
x: theData.findOne({_id: theID}, function(err,result){
if(!result){
return {}
};
}),
};
}, App);
What am I doing wrong and how should I be resolving this error? Is this a meteor specific error?
Any help is greatly appreciated!
What kind of error are you exactly trying to handle with your callback?
Meteor's findOne is different from node's mongodb driver's findOne that the post you link to uses.
The expected signature is:
collection.findOne([selector], [options])
There is no callback involved, since the method runs synchronously (but is reactive).
If you want to return a default value when the document is not found, you can simply use a JS logical OR:
// Provide an alternative value on the right that will be used
// if the left one is falsy.
theData.findOne({_id: theID}) || {};
A more rigorous approach would be to compare its type with
typeof queryResult === 'undefined'
Note that if theData collection is fed by the above subscription Meteor.subscribe('thePubSub'), I doubt Meteor will have time to populate the collection on the client by the time you query it…
as you see below, in my publish method I need to do a join twice, and the way I've done it throws an "Error: Publish function returned an array of non-Cursors" exception. and it is not even reactive!
is it possible to do it in a better way, I am very performance conscious and don't want to do 3 trips back and force between client and server, also some packages hit the database once for every item in an array!!
Meteor.publish('post', function(id) {
Posts.incView(id);
parentPost= Posts.findOne({_id: id});
console.log(parentPost);
eachPost= Posts.find({_id: {$in : parentPost.childs }});
users=[parentPost.createdBy];
eachPost.forEach( function(each) { users.push(each.users)});
return [
parentPost,
eachPost,
UInfo.find({_id:{$in:users}})
];
});
findOne() returns an object, not a cursor, which is why you're getting an error. Change findOne code to:
parentPost = Posts.find({_id: id});
This way you'll return three cursors.
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 am not able to update my mongoose schema because of a CastERror, which makes sence, but I dont know how to solve it.
Trip Schema:
var TripSchema = new Schema({
name: String,
_users: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
User Schema:
var UserSchema = new Schema({
name: String,
email: String,
});
in my html page i render a trip with the possibility to add new users to this trip, I retrieve the data by calling the findById method on the Schema:
exports.readById = function (request, result) {
Trip.findById(request.params.tripId).populate('_users').exec(function (error, trip) {
if (error) {
console.log('error getting trips');
} else {
console.log('found single trip: ' + trip);
result.json(trip);
}
})
};
this works find. In my ui i can add new users to the trip, here is the code:
var user = new UserService();
user.email = $scope.newMail;
user.$save(function(response){
trip._users.push(user._id);
trip.$update(function (response) {
console.log('OK - user ' + user.email + ' was linked to trip ' + trip.name);
// call for the updated document in database
this.readOne();
})
};
The Problem is that when I update my Schema the existing users in trip are populated, means stored as objects not id on the trip, the new user is stored as ObjectId in trip.
How can I make sure the populated users go back to ObjectId before I update? otherwise the update will fail with a CastError.
see here for error
I've been searching around for a graceful way to handle this without finding a satisfactory solution, or at least one I feel confident is what the mongoosejs folks had in mind when using populate. Nonetheless, here's the route I took:
First, I tried to separate adding to the list from saving. So in your example, move trip._users.push(user._id); out of the $save function. I put actions like this on the client side of things, since I want the UI to show the changes before I persist them.
Second, when adding the user, I kept working with the populated model -- that is, I don't push(user._id) but instead add the full user: push(user). This keeps the _users list consistent, since the ids of other users have already been replaced with their corresponding objects during population.
So now you should be working with a consistent list of populated users. In the server code, just before calling $update, I replace trip._users with a list of ObjectIds. In other words, "un-populate" _users:
user_ids = []
for (var i in trip._users){
/* it might be a good idea to do more validation here if you like, to make
* sure you don't have any naked userIds in this array already, as you would
*/in your original code.
user_ids.push(trip._users[i]._id);
}
trip._users = user_ids;
trip.$update(....
As I read through your example code again, it looks like the user you are adding to the trip might be a new user? I'm not sure if that's just a relic of your simplification for question purposes, but if not, you'll need to save the user first so mongo can assign an ObjectId before you can save the trip.
I have written an function which accepts an array, and in callback returns with an array of ObjectId. To do it asynchronously in NodeJS, I am using async.js. The function is like:
let converter = function(array, callback) {
let idArray;
async.each(array, function(item, itemCallback) {
idArray.push(item._id);
itemCallback();
}, function(err) {
callback(idArray);
})
};
This works totally fine with me, and I hope should work with you as well
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.