Sails.js Waterline Update Method - mongodb

I'm using Waterline in my Sails.js App to query my mongo database. I'm able to get a record out based on multiple query paramaters, such as this:
Model.findOne({param1: params[0], param2: params[1]})...
That works great. I looked at the docs for .update() and copied that but it isn't working. I still need to update a record based on two parameters, so this is my update function:
Model.update(
[{param1: params[0]}, {param2: params[1]}],
[{field1: update[0]}, {field2: update[1]}]
).exec(function(err, updatedModel) {
console.log(err);
console.log(updatedModel);
});
From what I read, it looks like the first argument to the update method is an object or array of objects with the values you're querying on to update a record; then the second parameter is the updated fields.
I'm not getting an error or an updatedModel, however, which probably means that I'm not finding an object from the Model table in mongo that fits the requirements. But I know that the object is there and the parameters are correct. Any suggestions?
Also, here's a gist with the update function as well.

Params in update function are objects not arrays.
Model.update(
{param1: params[0], param2: params[1]}, //this is find
{field1: update[0], field2: update[1]} // this is update
).exec(function(err, updatedModel) {
if(err)
console.error(err);
console.log(updatedModel);
});

Related

MongoDb: Insert Document in collection only if collection has no newer document since point in time

I want to depict the following use case using MongoDb:
I want to read from a collection and memorize that particular point in time.
When writing the next time to that collection, I want to not be able to write a new document, if another document has been added to that collection in between.
Using a timestamp property on the documents would be ok.
Is this possible?
One trick is use findAndModify
Assume at the time of reading, your most recent timestamp on a document is oldTimestamp:
db.collection.findAndModify({
query: {timestamp: {$gt: oldTimestamp}},
new: true, // Return modified / inserted document
upsert: true, // Update if match found, insert otherwise
update: {
$setOnInsert: {..your document...}
}
})
This will not insert your document if another document is inserted between your read and write operation.
However, this won't let you know that the document is inserted or not directly.
You should compare returned document with your proposed document to find that out.
In case using nodejs driver, the correct pattern should be:
collection.findAndModify(criteria[, sort[, update[, options]]], callback)
According to the example, our query should be:
db.collection('test').findAndModify(
{timestamp: {$gt: oldTimestamp}}, // query, timestamp is a property of your document, often set as the created time
[['timestamp','desc']], // sort order
{$setOnInsert: {..your document..}}, // replacement, replaces only the field "hi"
{
new: true,
upsert: true
}, // options
function(err, object) {
if (err){
console.warn(err.message); // returns error if no matching object found
}else{
console.dir(object);
}
});
});
This can be achieved, using a timestamp property in every document. You can take a look at the Mongoose Pre Save path validation hook . Using this hook, you can write something like this.
YourSchema.path('timestamp').validate(function(value, done) {
this.model(YourSchemaModelName).count({ timestamp: {$gt : value} }, function(err, count) {
if (err) {
return done(err);
}
// if count exists and not zero hence document is found with greater timestamp value
done(!count);
});
}, 'Greater timestamp already exists');
Sounds like you'll need to do some sort of optimistic locking at the collection level. I understand you are writing new documents but never updating existing ones in this collection?
You could add an index on the timestamp field, and your application would need to track the latest version of this value. Then, before attempting a new write you could lookup the latest value from the collection with a query like
db.collection.find({}, {timestamp: 1, _id:0}).sort({timestamp:-1}).limit(1)
which would project just the maximum timestamp value using a covered query which is pretty efficient.
From that point on, it's up to your application logic to handle the 'conflict'.

What does Mongoose update() really update?

The Mongoose docs mention this example code:
Tank.findById(id, function (err, tank) {
if (err) return handleError(err);
tank.size = 'large';
tank.save(function (err, updatedTank) {
if (err) return handleError(err);
res.send(updatedTank);
});
});
Does this result to a MongoDB update() command setting just the size property (i.e. db.tanks.update({_id: tank._id}, {$set: {size: "large"}});) or does this replace the whole document?
Or, with other words, what if another client updates another property (let's say, color) just after findById() returns but before before save() is executed? Is color then reverted to the previous value or kept intact?
And, in case Mongoose is smart enough to update only "changed" properties, how exactly is that detected (values being compared before/after? or via a setter on the tank object?)? What if the tank size was already "large" anyway?
I'm asking because the Mongoose docs are not very clear on this and I'd like to understand how Mongoose is designed and intended to be used in this regard.
A bit of testing shows that Mongoose tracks modifications to documents (by using setters, as you already mentioned), and will run an update that $set's the new values.
So this:
tank.size = 'large';
tank.save(...);
Is (roughly speaking) implemented as:
tankCollection.update({ _id : tank._id }, { $set : { size : 'large' } }, ...);
If the previous value of size was also large, it won't perform the update. Also, if the document is new, it will run a different code path (calling tankCollection.insert()).
So concurrent updates should work, provided that they don't update the same property (in which case the last performed update wins).

Mongoose/MongoDB: Can I update a document if I have a reference to it already without a query?

I perform db.collection.findOne( {query}... In the callback, if I successfully find an object, I want to modify one of it's properties...
,function(err, obj){
//is there a better way to do this since I already have a reference to the object? Searching the docs always gives me db.collection APIs
db.collection.update( {_id: obj._id }, { modifications}, function(err, obj){ ... }
}
Your subject includes mongoose so I guess you are using mongoose.
So there are two good ways you can do that (of course there are much more ways to do that):
Use findOneAndUpdate
model.findOneAndUpdate(query, {modifications}, function(err, obj) { //callback } )
read more about it here
your returned obj in the callback is a mongoose model, so you can modify it and save it:
function(err, obj){
obj.some_field = 'blabla';
obj.save();
}
Sure you can.
You can also use the findOneAndUpdate to use a single query. If nothing is found, nothing should be modified.

Cannot remove on mongodb using mongoose?

Hi im trying to simply remove a document from a collection using mongoose but for some strange reason I cannot get it to work.
Here is the code:
function deleteUserevent()
{console.log('in delete User Event');
models.Userevent.remove({ _id: "5214f4050acb53fe31000004"}, function(err) {
if (!err){
console.log('deleted user event!');
}
else {
console.log('error');
}
});
}
Can anyone help me out on my syntax? I know the _id is stored as new ObjectId("5214f4050acb53fe31000004") but I have tried this with no joy?
Thanks.
In MongoDB, the "_id" field of documents is of type ObjectId, as you mentioned. This is not equal to a String, so running the query
db.userevent.remove({ _id: "5214f4050acb53fe31000004"});
will not match anything, and will not remove anything. Instead, you must search for a document where the _id field is an ObjectId with that value:
db.userevents.remove({ _id: ObjectId("5214f4050acb53fe31000004")});
In mongoose, you can use the findByIdAndRemove command to remove a document with a specific _id. This command takes either an ObjectId or a String as an argument, so
query = Userevent.findByIdAndRemove("5214f4050acb53fe31000004");
should work just fine.
Just add exec() after query.
It should work like this:
await models.Userevent.findByIdAndDelete("5214f4050acb53fe31000004").exec()

Finding an Embedded Document by a specific property in Mongoose, Node.js, MongodDB

For this app, I'm using Node.js, MongoDB, Mongoose & Express
So I have a Param Object that contains an array of Pivots, and I want to read certain data from the pivots as outlined below
---in models.js-------------------------
var Pivot = new Schema({
value : String
, destination : String
, counter : Number
});
var Param = new Schema({
title : String
, desc : String
, pivots : [Pivot]
});
------------- in main.js --------------
var Param = db.model('Param');
app.get('/:title/:value', function(req, res){
Param.findOne({"title":req.param('title')}, function(err, record){
console.log(record.pivots);
record.pivots.find({"value":req.param('value')}, function(err, m_pivot){
pivot.counter++;
res.redirect(m_pivot.destination);
});
record.save();
});
});
I know that the code works until console.log(record.pivots), since i got a doc collection with the right pivot documents inside.
However, there does not seem to be a find method to let me match an embedded document by the 'value' property defined in the schema. Is it possible to search through this array of embedded documents using .find() or .findOne() , and if not, is there some easy way to access it through mongoose?
varunsrin,
This should do it
app.get('/:title/:value', function(req, res) {
Param.findOne({'pivots.value': req.param('value'), "title":req.param('title')}},
function(err, record) {
record.pivot.counter++;
res.redirect(m_pivot.destination);
record.save();
});
});
Note the pluralization of the query to match the field name in your schema
You can querying using embedded document properties like this:
{'pivot.value': req.param('value')}}
Update in response to comment:
app.get('/:title/:value', function(req, res) {
Param.findOne({'pivot.value': req.param('value'), "title":req.param('title')}},
function(err, record) {
record.pivot.counter++;
res.redirect(m_pivot.destination);
record.save();
});
});
I solved it temporarily using a simple for loop to parse the object array as follows:
for (var i=0; i <record.pivots.length; i++){
if (record.pivots[i].value == req.param('value')){
res.redirect(record.pivots.destination);
}
}
However, I still think that Mongoose must have a simpler way of interacting with embedded documents - and this loop is somewhat slow, especially when the number of embedded documents grows large.
If anyone has any suggestions for a faster way to search this object array either in js or with a mongoose function, please post below.
the biggest problem with this is that if your req has some fields empty (that should act as wildcard), you will not find anything since mongo tries to match empty params as well, so searching for {"user":"bob", "color":""} is not the same as {"user":"bob", "color":"red"} or {"user":"bob"}. this means that you have to first create a query object and filter out any unused parameters before you pass it in, and if you create a query object, you can no longer do something like "user.name=..." because mongo interperets this as an error since it does not first resolve the object literal into a string.
Any ideas on this problem?
ps. You'd think it would be easy enough to make an object like:
user.name="bob"; user.color:"green"; user.signup.time="12342561"
and then just use user as a query object :/
I think you are looking for the "$in" keyword?
As in:
{a: {$in: [10, "hello"]}}
source: MongoDB Queries CheatSheet