[SOLVED] MongoDB w/ Simple Schema: $inc error

I am trying to update the database using $inc to increment a value by 1 when a certain action happens. I am new to MeteorJS, basically teaching myself by extending the functionality of the advanced Todo app and reading guides. I am using SimpleSchema to validate input (since that’s what the Todo app uses).

Just by looking at this, can anyone tell why this would not work?

export const subtractCount = new ValidatedMethod({
  name: 'todos.subtractCount',
  validate: new SimpleSchema({
    todoId: { type: String },
  }).validator(),
  run({ todoId }) {
    Todos.update(todoId, { $inc: { addedCount: 1 } });
  },
});

This is the error I get: Uncaught Error: Match error: Unknown key in field $inc
The following snippet of code using $set instead of $inc works perfectly fine which makes me think it’s something simple that I’m missing.

export const subtractCount = new ValidatedMethod({
  name: 'todos.subtractCount',
  validate: new SimpleSchema({
    todoId: { type: String },
  }).validator(),
  run({ todoId }) {
    Todos.update(todoId, { $set: { addedCount: 1 } });
  },
});

The code snippets above are in a file called methods.js that is imported into the server folder (it’s running on the server), and they are called by the client when a click event happens.

Thanks, in advanced, for the help!

Using $set like that means the addedCount for the Todo will always be 1.

I just tried Collection.update('GQMs8MaSQaXFBW8tE', {$inc: {field: 1}}) in the console of my application and it worked.

Do you have Todos.allow setup?

I have Todos.deny set up in a file called todos.js to prevent the client from making changes to the db, but I don’t think I have Todos.allow set up anywhere.

I am able to get the command Collection.update(‘GQMs8MaSQaXFBW8tE’, {$inc: {field: 1}}) to work in the console too, but it just doesn’t want to work when I call the function from MyItems.jsx in the ui folder. Also, I mentioned how $set worked just to show that one command was working while $inc wasn’t. You can see it being used correctly, here.

export const incrementCount = new ValidatedMethod({
  name: 'todos.incrementCount',
  validate: new SimpleSchema({
    todoId: { type: String },
    itemCount: {type: Number},
  }).validator(),
  run({ todoId, itemCount }) {
    Todos.update(todoId, { $set: { addedCount: itemCount + 1 } });
  },
});

I am passing the itemCount to the incrementCount function from MyItems.jsx which worked when calling it form MyItems.jsx but not from MyOtherItems.jsx because I did not have access to the collection in MyOtherItems.jsx. That is why I am going back to trying $inc.

Maybe it is more complicated than I thought. Thanks for the response!

The reason I was asking about allow/deny was that the error you posted

Uncaught Error: Match error: Unknown key in field $inc

seems to indicate a test being performed that determines the collection has no field addedCount. Does your collection have a field addedCount already? If not, that might be the reason.

PS: please put your code between triple back-ticks, so it gets formatted correctly

That is exactly what I thought, but I don’t know why the validation would pass when using $set and fail when using $inc. Here’s the entire file of todos.js.

class TodosCollection extends Mongo.Collection {
  insert(doc, callback) {
    console.log(doc);
    const ourDoc = doc;
    ourDoc.createdAt = ourDoc.createdAt || new Date();
    const result = super.insert(ourDoc, callback);
    incompleteCountDenormalizer.afterInsertTodo(ourDoc);
    return result;
  }
  update(selector, modifier) {
    const result = super.update(selector, modifier);
    incompleteCountDenormalizer.afterUpdateTodo(selector, modifier);
    return result;
  }
  remove(selector) {
    const todos = this.find(selector).fetch();
    const result = super.remove(selector);
    incompleteCountDenormalizer.afterRemoveTodos(todos);
    return result;
  }
}

export const Todos = new TodosCollection('Todos');

// Deny all client-side updates since we will be using methods to manage this collection
Todos.deny({
  insert() { return true; },
  update() { return true; },
  remove() { return true; },
});

Todos.schema = new SimpleSchema({
  listId: {
    type: String,
    regEx: SimpleSchema.RegEx.Id,
    denyUpdate: true,
  },
  text: {
    type: String,
    max: 500,
  },
  createdAt: {
    type: Date,
    denyUpdate: true,
  },
  checked: {
    type: Boolean,
    defaultValue: false,
  },
  addedCount: {
    type: Number,
    defaultValue: parseInt(0),
  },
});

Todos.attachSchema(Todos.schema);

// This represents the keys from Lists objects that should be published
// to the client. If we add secret properties to List objects, don't list
// them here to keep them private to the server.
Todos.publicFields = {
  listId: 1,
  text: 1,
  createdAt: 1,
  checked: 1,
  addedCount: 1,
};

I must be missing something pretty obvious. I might just take a look at it tomorrow when I have a fresh set of eyes.

In this line:

Todos.update(todoId, { $set: { addedCount: addedCount + 1 } });

From where the addedCount on the value field comes from? It should not be { addedCount: itemCount + 1 } ?

Yes, you are right. I updated my response. I changed the names of the variables around to make more sense, but I missed that one.

Update.
I didn’t realize it, but $inc was actually incrementing the value in the MongoDB. Now, I just have to figure out why the error “Uncaught Error: Match error: Unknown key in field $inc” is being thrown.

The problem was in the update function in the following class.

class TodosCollection extends Mongo.Collection {
  insert(doc, callback) {
    console.log(doc);
    const ourDoc = doc;
    ourDoc.createdAt = ourDoc.createdAt || new Date();
    const result = super.insert(ourDoc, callback);
    incompleteCountDenormalizer.afterInsertTodo(ourDoc);
    return result;
  }
  update(selector, modifier) {
    const result = super.update(selector, modifier);
    incompleteCountDenormalizer.afterUpdateTodo(selector, modifier);
    return result;
  }
  remove(selector) {
    const todos = this.find(selector).fetch();
    const result = super.remove(selector);
    incompleteCountDenormalizer.afterRemoveTodos(todos);
    return result;
  }
}

I forgot that the update function called incompleteCountDenormalizer with the same params (the selector and modifier) which is where the error came from.
incompleteCountDenormalizer was calling an update function with todoId and { $inc: { addedCount: 1 } }.
I wish Meteor could’ve told me which file and which line the error was coming from, but I guess it comes with the territory of using frameworks like Meteor.js.

In cases where I’m seeing a message without file/line info like that it is usually because an error handler caught it. In Chrome DevTools, you can often get right to the bottom of these by selecting “Pause on exceptions” and then turning on “Pause on caught exceptions”.

Interesting. Thanks for the tip! I tried it out, but it looks like a lot of errors are being thrown, and I couldn’t find the specific one that I was looking for. I’ll have to start up another fresh Todo example app to see if the errors are coming from my code or the app’s code. Thanks, again!