Situation

Recently we ran into the situation were we wanted to remove an item from an embedded Array of Objects in a Meteor Mongo Collection - on the server
Easy task at first glance because you’d expect to be able to use Mongo’s inbuilt $pull option in a Collection.update() query, right?

1
2
3
4
Collection.update(
{_id: selector},
{ $pull: { array: { elementId : id }}}
);

Using this on the server lead to the following error however:

Error: Update parameter cannot have both modifier and non-modifier fields.

Complication

Meteor provides Mongo access under the hood via a package called MiniMongo.
The default implementation of Meteor’s MiniMongo implementation of Collection.update() does not allow for the usage of $pull our this particular way.
In the Meteor Docs we can read that:

$pull in modifiers can only accept certain kinds of selectors.

So, I guess that our selector was not one of them…

Solution 1 (server)

Luckily - on the server - we have access to the native mongodb driver in the form of Collection.rawCollection().
This gives us access to all kinds of helpful Mongo methods that have not been implemented in MiniMongo (yet).
This was a suitable solution for us as for the specific method, it was a server only method that was not stubbed on the client for latency compensation.
Using the following pattern thus - gives us the desired result:

1
2
3
4
5
6
7
var raw = Collection.rawCollection();
var findOneAndUpdate = Meteor.wrapAsync(raw.findOneAndUpdate, raw);
findOneAndUpdate({_id: selector},
{
$pull: { array : { elementId: id }}
});

Solution 2 (server + client)

Since, on the client you don’t have access to Collection.rawCollection() you would run into an error on the client when using the above approach in client code, or in a latency compensated Method call that executes on client & server.
You could fall back on a library like underscore of lodash to reject the documents you would want to have ‘pulled’:

1
2
3
4
5
6
7
8
9
10
var filteredArray = _.reject(Collection.findOne({_id: selector}), function(col) {
// reject all array elements were id is that of the one you would actually want to 'pull'
return col.array.elementId === id;
});
Collection.update({_id: selector}, {
$set: {
array: filteredArray
}
});

Note: Please bear in mind that concurrency / read/write concern could become a problem here. Use wisely!