deleting specific json element - mongodb - node.js

i want to delete one specific point out of my db
// DB layout
{
id: 9243
email: asd#asd.asd
//stuff
point0:{
//stuff
point1:{
//stuff
point2:{
//stuff (dynamic)
}
}
}
}
now i want to delete point2 out of my db - without! knowing whats inside of it
//actual state of dev:
collection.update({'email': mail}, { $pull: elem }, function(err){
//tested:
elem = { "point0.point1" : "point2" }
elem = "point0.point1.point2"
elem = { "point0.point1.point2" : "" }
pullAll
... etc

have you tried using $unset to remove the value point2?
collection.update({'email' : mail}, {$unset : {'point0.point1.point2' : 0}}, function (err) {})

Related

Add object to array of objects if object doesn't exist, otherwise update object

Desired Behaviour
Add object to array of objects if it doesn't exist, otherwise update object.
Each user can only add one rating.
Schema
"ratings" : [
{
"user_email" : "john#smith.com",
"criteria_a" : 0,
"criteria_b" : 3,
"criteria_c" : 1,
"criteria_d" : 5,
"criteria_e" : 2
},
{
"user_email" : "bob#smith.com",
"criteria_a" : 1,
"criteria_b" : 3,
"criteria_c" : 5,
"criteria_d" : 2,
"criteria_e" : 1
},
{
"user_email" : "jane#smith.com",
"criteria_a" : 5,
"criteria_b" : 3,
"criteria_c" : 1,
"criteria_d" : 0,
"criteria_e" : 1
}
]
What I've Tried
I've read this:
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
Source: https://docs.mongodb.com/manual/reference/operator/update/addToSet/
But I'm a bit confused because I think I am trying to:
$addToSet if object doesn't exist
$set (ie update the existing object) if it does exist
The following keeps adding rating objects to the array, even if a user has already added a rating.
var collection = mongo_client.db("houses").collection("houses");
var o_id = new ObjectID(id);
var query = { _id: o_id, "ratings.user_email": user_email };
var update = { $addToSet: { ratings: rating } };
var options = { upsert: true };
collection.updateOne(query, update, options, function(err, doc) {
if (err) {
res.send(err);
} else {
res.json({ doc: doc })
}
});
Edit:
The following works if a user has already submitted a rating, but otherwise it throws the error The positional operator did not find the match needed from the query.:
var update = { $set: { "ratings.$": rating } };
You will need to perform this with two distinct actions:
Attempt a $push if no rating for the given user exists yet, i.e. negate the user_email in your match portion of the update query as you're already doing.
Perform a second update once again matching on "ratings.user_email": user_email, but this time perform a $set on "ratings.$": rating.
If no rating for the user exists yet, then (1) will insert it, otherwise no operation occurs. Now that the rating is guaranteed to exist, (2) will ensure that the rating is updated. In the case that (1) inserted a new rating, (2) will simply result in no operation occuring.
The result should look something like this:
collection.updateOne(
{ _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } },
{ $push: { ratings: rating } }
);
collection.updateOne(
{ _id: o_id, "ratings.user_email": user_email },
{ $set: { "ratings.$": rating } }
);
A more verbose example with comments is below:
// BEGIN new rating query/update variables
var query_new_rating = { _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } };
var update_new_rating = { $push: { ratings: rating } };
// END new rating query/update variables
// part 01 - attempt to push a new rating
collection.updateOne(query_new_rating, update_new_rating, function(error, result) {
if (error) {
res.send(error);
} else {
// get the number of modified documents
// if 0 - the rating already exists
// if 1 - a new rating was added
var number_modified = result.result.nModified;
// BEGIN if updating an existing rating
if (number_modified === 0) {
console.log("updating existing rating");
// BEGIN existing rating query/update variables
var query_existing_rating = { _id: o_id, "ratings.user_email": user_email };
var update_existing_rating = { $set: { "ratings.$": rating } };
// END existing rating query/update variables
// part 02 - update an existing rating
collection.updateOne(query_existing_rating, update_existing_rating, function(error, result) {
if (error) {
res.send(error);
} else {
console.log("updated existing rating");
res.json({ result: result.result })
}
});
}
// END if updating an existing rating
// BEGIN if adding a new rating
else if (number_modified === 1) {
console.log("added new rating");
res.json({ result: result.result })
}
// END if adding a new rating
}
});

Can't find a easy way out of multiple async for each node js (sails)

So here's the deal :
I have an array of objects with a child array of objects
askedAdvices
askedAdvice.replayAdvices
I'm looping trough the parent and foreach looping trough the childs and need to populate() two obejcts (I'm using sails)
The child looks like :
askedAdvices = {
replayAdvices : [{
bookEnd : "<ID>",
user : "<ID>"
}]
}
So my goal is to cycle and populate bookEnd and user with two findOne query, but I'm going mad with the callback hell.
Here's the Models code :
AskedAdvices Model
module.exports = {
schema : false,
attributes: {
bookStart : {
model : 'book'
},
replayAdvices : {
collection: 'replybookend'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
ReplyBookEnd Model
module.exports = {
schema : false,
attributes: {
bookEnd : {
model : 'book'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
Here's the Method code :
getAskedAdvices : function(req, res) {
var queryAskedAdvices = AskedAdvices.find()
.populate("replayAdvices")
.populate("user")
.populate("bookStart")
queryAskedAdvices.exec(function callBack(err,askedAdvices){
if (!err) {
askedAdvices.forEach(function(askedAdvice, i){
askedAdvice.replayAdvices.forEach(function(reply, i){
async.parallel([
function(callback) {
var queryBook = Book.findOne(reply.bookEnd);
queryBook.exec(function callBack(err,bookEndFound) {
if (!err) {
reply.bookEnd = bookEndFound;
callback();
}
})
},
function(callback) {
var queryUser = User.findOne(reply.user)
queryUser.exec(function callBack(err,userFound){
if (!err) {
reply.user = userFound;
callback();
}
})
}
], function(err){
if (err) return next(err);
return res.json(200, reply);
})
})
})
} else {
return res.json(401, {err:err})
}
})
}
I can use the async library but need suggestions
Thanks folks!
As pointed out in the comments, Waterline doesn't have deep population yet, but you can use async.auto to get out of callback hell. The trick is to gather up the IDs of all the children you need to find, find them with single queries, and then map them back onto the parents. The code would look something like below.
async.auto({
// Get the askedAdvices
getAskedAdvices: function(cb) {
queryAskedAdvices.exec(cb);
},
// Get the IDs of all child records we need to query.
// Note the dependence on the `getAskedAdvices` task
getChildIds: ['getAskedAdvices', function(cb, results) {
// Set up an object to hold all the child IDs
var childIds = {bookEndIds: [], userIds: []};
// Loop through the retrieved askedAdvice objects
_.each(results.getAskedAdvices, function(askedAdvice) {
// Loop through the associated replayAdvice objects
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
childIds.bookEndIds.push(replayAdvice.bookEnd);
childIds.userIds.push(replayAdvice.user);
});
});
// Get rid of duplicate IDs
childIds.bookEndIds = _.uniq(childIds.bookEndIds);
childIds.userIds = _.uniq(childIds.userIds);
// Return the list of IDs
return cb(null, childIds);
}],
// Get the associated book records. Note that this task
// relies on `getChildIds`, but will run in parallel with
// the `getUsers` task
getBookEnds: ['getChildIds', function(cb, results) {
Book.find({id: results.getChildIds.bookEndIds}).exec(cb);
}],
getUsers: ['getChildIds', function(cb, results) {
User.find({id: results.getChildIds.userIds}).exec(cb);
}]
}, function allTasksDone(err, results) {
if (err) {return res.serverError(err);
// Index the books and users by ID for easier lookups
var books = _.indexBy(results.getBookEnds, 'id');
var users = _.indexBy(results.getUsers, 'id');
// Add the book and user objects back into the `replayAdvices` objects
_.each(results.getAskedAdvices, function(askedAdvice) {
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
replayAdvice.bookEnd = books[replayAdvice.bookEnd];
replayAdvice.user = users[replayAdvice.bookEnd];
});
});
});
Note that this is assuming Sails' built-in Lodash and Async instances; if you're using newer versions of those packages the usage of async.auto has changed slightly (the task function arguments are switched so that results comes before cb), and _.indexBy has been renamed to _.keyBy.

Mongoose update or create many values in one query

I have a document like this:
{
"_id" : ObjectId("565e906bc2209d91c4357b59"),
"userEmail" : "abc#example.com",
"subscription" : {
"project1" : {
"subscribed" : false
},
"project2" : {
"subscribed" : true
},
"project3" : {
"subscribed" : false
},
"project4" : {
"subscribed" : false
}
}
}
I'm using express to for my post web service call like this:
router.post('/subscribe', function(req, res, next) {
MyModel.findOneAndUpdate(
{
userEmail: req.body.userEmail
},
{
// stuck here on update query
},
{
upsert: true
}, function(err, raw) {
if (err) return handleError(err);
res.json({result: raw});
}
)
});
My req contains data like this:
{
userEmail: "abc#example.com",
subscription: ["project1", "project4"]
}
So these are the steps I would like to perform on this call:
Check user exists, otherwise create the user. For instance, if abc#example.com doesn't exist, create a new document with userEmail as abc#example.com.
If user exists, check project1 and project4 exists in subscription object. If not create those.
If project1 and project4 exists in subscription, then update the subscribed to true.
I'm not sure whether I can achieve all the above 3 steps with a single query. Kindly advise.
Vimalraj,
You can accomplish this using $set. The $set operator replaces the value of a field with the specified value. According to the docs:
If the field does not exist, $set will add a new field with the specified value, provided that the new field does not violate a type constraint. If you specify a dotted path for a non-existent field, $set will create the embedded documents as needed to fulfill the dotted path to the field.
Since your req.subscription will be an array you'll have to build your query. to look like this:
{
$set: {
"subscription.project1":{"subscribed" : true},
"subscription.project4":{"subscribed" : true}
}
You can use reduce to create an object from req.subscription = ["project1","project4"] array
var subscription= req.subscription.reduce(function(o,v){
o["subscription." + v] = {subscription:true};
return o;
},{});
Then your code becomes:
router.post('/subscribe', function(req, res, next) {
var subscription= req.subscription.reduce(function(o,v){
o["subscription." + v] = {subscription:true};
return o;
},{});
MyModel.findOneAndUpdate(
{
userEmail: req.body.userEmail
},
{
$set: subscription
},
{
upsert: true
}, function(err, raw) {
if (err) return handleError(err);
res.json({result: raw});
}
)
});

Is it possible to get count of number of docs returned from find() query in mongoose

I am trying to get count of data fetched from the database using find() query in mongoose. Now can anyone tell me can i do something like below or do i have to write other function to do that
merchantmodel.find({merchant_id: merchant_id, rating: {'$ne': -1 }, review: {'$ne': "" }}, {'review':1, '_id':0}, {sort: {time_at: -1}}, function(err, docs) {
if (err) {
} else {
if (docs) {
console.log(docs[1].review);
console.log(docs.size()); // Here by writing something is it possible to get count or not
res.json({success: 1, message : "Successfully Fetched the Reviews"});
}
}
});
Convert returned value to array and then use length property
var query = { merchant_id : merchant_id, rating : { '$ne': -1 }, review: { '$ne': "" }};
var projection = { 'review':1, '_id':0 };
var options = { sort: { time_at: -1 } };
merchantmodel.find(query, projection, options).toArray(function(err, docs) {
if (err) {
throw(err);
}
console.log(docs[1].review);
console.log(docs.length);
res.json({success: 1, message : "Successfully Fetched the Reviews"});
});
You can simply do this:
console.log(docs.length);
The docs variable returned by the find() method is an array so docs.length would do the job.
The mongodb native way to do this would be:
db.collection.find( { a: 5, b: 5 } ).count()

Mongo: Traverse error when updating object inside nested array

I am trying to update the floor_num value from 1 to 9000 in an object in a nested array, in Mongoose:
thingSchema.findById(thingID, function(err, lm) {
if (!lm){
console.log(err);
}
else {
lm.update({'style.maps.localMapArray.map_marker_viewID': req.body.map_marker_viewID},
{'$set': {
'style.maps.localMapArray.$.floor_num': 9000,
}
}, function(err) {
//update success
});
}
});
But I'm getting this Mongo error:
MongoError: cannot use the part
(localMapArray of style.maps.localMapArray.map_marker_viewID) to traverse
the element ({localMapArray: [ { map_marker_viewID: "acympqswmkui",
floor_num: 1 } ]} code: 16837
My schema:
var thingSchema = new Schema({
style: {
maps: {
localMapArray: [{
map_marker_viewID : String,
floor_num : Number
}],
}
}
});
Here is my noob way of solving such kind of problems. I am also new to MongoDB and Mongoose.
thingSchema.findById(thingID, function(err, lm) {
if (err)
console.log(err);
var localMaps = lm.style.maps.localMapArray;
for (var i = 0; i < localMaps.length; i++) {
if (lm.style.maps.localMapArray[i].map_marker_viewId == req.body.map_marker_viewID) {
lm.style.maps.localMapArray[i].floor_num = 9000;
}
}
lm.save();
});
I wish somewhere here can give better example with Mongoose. Hope it will help you.

Resources