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.
Related
I'm completely new to these technologies, and am having trouble wrapping my head around it, so bear with me. So, my situation is that I've deployed Hasura on Heroku and have added some data, and am now trying to implement some functionality where I can add and edit certain rows of a table. Specifically I've been following this from Hasura, and this from vue-apollo.
I've implemented the adding and editing (which works), and now want to also reflect this in the table, by using the update property of the mutation and updating the cache. Unfortunately, this is where I get lost. I'll paste some of my code below to make my problem more clear:
The mutation for adding a player (ADD_PLAYER_MUTATION) (same as the one in Hasura's documentation linked above):
mutation addPlayer($objects: [players_insert_input!]!) {
insert_players(objects: $objects) {
returning {
id
name
}
}
}
The code for the mutation in the .vue file
addPlayer(player, currentTimestamp) {
this.$apollo.mutate({
mutation: PLAYER_ADD_MUTATION,
variables: {
objects: [
{
name: player.name,
team_id: player.team.id,
created_at: currentTimestamp,
updated_at: currentTimestamp,
role_id: player.role.id,
first_name: player.first_name,
last_name: player.last_name
}
]
},
update: (store, { data: { addPlayer } }) => {
const data = store.readQuery({
query: PLAYERS
});
console.log(data);
console.log(addPlayer);
data.players.push(addPlayer);
store.writeQuery({ query: PLAYERS, data });
}
});
},
I don't really get the update part of the mutation. In most examples the { data: { x } } bit uses the function's name in the place of x, and so I did that as well, even though I don't really get why (it's pretty confusing to me at least). When logging data the array of players is logged, but when logging addPlayer undefined is logged.
I'm probably doing something wrong that is very simple for others, but I'm obviously not sure what. Maybe the mutation isn't returning the correct thing (although I'd assume it wouldn't log undefined in that case), or maybe isn't returning anything at all. It's especially confusing since the player is actually added to the database, so it's just the update part that isn't working - plus, most of the guides / tutorials show the same thing without really much explanation.
Okay, so for anyone as stupid as me, here's basically what I was doing wrong:
Instead of addPlayer in update: (store, { data: { addPlayer } }), it should be whatever the name of the mutation is, so in this case insert_players.
By default a mutation response from Hasura has a returning field, which is a list, and so the added player is the first element in the list, so you can get it like so: const addedPlayer = insert_players.returning[0];
I didn't want to just delete my question after realising what was wrong shortly after posting it, in case this is useful to other people like me, and so I'll leave it up.
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.
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...);
},
});
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.
Making asynchronous requests in a loop to delete documents from an embedded collection:
_.each deletedItem, (item) ->
item.$delete()
Erratically throws this error:
{ message: 'No matching document found.', name: 'VersionError' }
When executing:
var resume = account.resumes.id(id);
resume.remove();
account.save(function (err, acct) {
console.log(err);
if(err) return next(err);
res.send(resume);
});
After logging account.resumes and looking through the _id's of all of the resumes, the document I am attempting to find by id, exists in the collection.
530e57a7503d421eb8daca65
FIND:
{ title: 'gggff', _id: 530e57a7503d421eb8daca65 }
IN:
[{ title: 'asddas', _id: 530e57a7503d421eb8daca61 }
{ title: 'gggff', _id: 530e57a7503d421eb8daca65 }
{ title: 'ewrs', _id: 530e57a7503d421eb8daca64 }]
I assume this has to do with the fact that I am performing these requests asynchronously, or that there is a versioning issue, but I have no idea how to resolve it.
It doesn't make any sense to me how when I log the resumes, I can see the resume I attempt to find, yet if I log:
log(account.resumes.id(id));
I get undefined.
UPDATE
I've discovered that my issue is with versioning.
http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html
But I am still unsure how to resolve it without disabling versioning, which I don't want to do.
In mongodb version 3, documents now have an increment() method which manually forces incrementation of the document version. This is also used internally whenever an operation on an array potentially alters array element position. These operations are:
$pull $pullAll $pop $set of an entire array
changing the version key
The version key is customizable by passing the versionKey option to the Schema constructor:
new Schema({ .. }, { versionKey: 'myVersionKey' });
Or by setting the option directly:
schema.set('versionKey', 'myVersionKey');
disabling
If you don’t want to use versioning in your schema you can disable it by passing false for the versionKey option.
schema.set('versionKey', false);
MongooseJs API docs explicitly warn on disabling versioning, and recommend against it. Your issue is due to workflow. If you're updating your collection from the UI, sending the API request and not refreshing your object with the object from the backend -- then attempt to update it again, you'll encounter the error you are reporting. I suggest either consuming/updating the object scope from the API response, then __v is correctly incremented. Or don't send the __v field on the PUT API request, this way it won't conflict with version on the collection in the database.
Another option is -- when requesting the object from the backend, have the API response not send the __v field, this way you don't have to code logic to NOT send it from the frontend. On all your gets for that collection, do either one of the following (depends how you write your queries):
var model = require('model');
var qry = model.find();
qry.select('-__v');
qry.exec(function(err,results){
if(err) res.status(500).send(err);
if(results) res.status(200).json(results);
});
OR
var model = require('model');
model.find({}, '-__v', function(err,results){
if(err) res.status(500).send(err);
if(results) res.status(200).json(results);
});