I think there are multiple ways to do this, and that has me a little confused as to why I can't get it to work.
I have a schema and I would like to update Notes within it but I can't seem to do it. Additionally, if I want to return the notes how would I go about doing it?
schema :
{
_id : 1234
email : me#me.com
pass : password
stock : [
{
Ticker : TSLA
Market : Nasdaq
Notes : [
"Buy at 700",
"Sell at 1000"
]
},
{
Ticker : AAPL
Market : Nasdaq
Notes : [
"Buy at 110",
"Sell at 140"
]
},
]
}
Each user has a list of stocks, and each stock has a list of notes.
Here is what I have tried in order to add a note to the list.
router.post(`/notes/add/:email/:pass/:stock/:note`, (req, res) => {
var email = req.params.email
var pass = req.params.pass
var note = req.params.note
var tempStock = req.params.stock
userModel.findOne({email: email} , (err, documents)=>{
if (err){
res.send(err);
}
else if (documents === null){
res.send('user not found');
}else if (bcrypt.compareSync(pass , documents.pass)){
userModel.findOneAndUpdate({email : email , "stock.Ticker" : tempStock}, {$push : {Notes : note}} ,(documents , err)=>{
if(err){
res.send(err);
}else {
res.send(documents.stock);
}
})
}
})
})
Thanks :)
Currently, you are pushing the new note into a newly created Notes property inside the model instead of into the Notes of the concrete stock. I am not completely aware of the mongoose semantics but you need something like this:
userModel.findOneAndUpdate({ email: email, "stock.Ticker": tempStock }, { $push: { "stock.$.Notes": note } }, (documents, err) => {
$ gives you a reference to the currently matched element from the stock array.
For the second part, I am not sure what you mean by
Additionally, if I want to return the notes how would I go about doing it?
They should be returned by default if you're not doing any projection excluding them.
Also, as per the docs(and general practise), the callback for the findOneAndUpdate has a signature of
(error, doc) => { }
instead of
(documents, err) => { }
so you should handle that.
Related
I'm sending the following object for mongoose to update:
{
"name" : "John",
"fruit" : "5a3d678d9b1549979d81c6ba" // this is an objectID
}
and the method:
db_user.findByIdAndUpdate(req.params.id, req.body).then(function(user, err) {
res.send(user);
});
This works fine if I replace the fruit with another fruitID which is what I'm trying to update.
However, if I send an object with no fruit, it won't remove the fruit field from the user document. Such as:
{
"name" : "John"
}
It will update whatever name I pass, but it won't remove the fruit field. Why?
Also: I can't be checking for each field that I'm sending or not and do an $unset for each accordingly, because I'm sending a lot of fields. I just want Mongoose to override the old document with the new one.
I know this is a little complicated / ugly and I'm sure there is an easier way but this should work if you want to update on specific thing and not have it fill the other values in blank.
function updateItem(req, res) {
function GetObjectFromKeyValuePairs(pairs) {
var tmp = {};
for(var key in pairs)
if(key[0] !== "_")
if(pairs[key].length !== 0)
tmp[`${key}`] = `${pairs[key]}`;
return tmp;
}
let updateOnlyChangedVals = GetObjectFromKeyValuePairs(req.body);
DB.Item.update({_id: req.params.id}, {$set: updateOnlyChangedVals},{new:true}, (err, uItem) => {
if (err) { return console.log("index error: " + err); }
res.json(uItem);
});
}
Node.JS, MONGODB, not using Mongoose.
I have a document I'm saving. When I use UPSERT, it structures the data as so :
{ "_id" : ObjectId("573a123f55e195b227e7a78d"),
"document" :
[ {"name" : "SomeName" } ]
}
When I use INSERT it inserts it all on the root level :
{ "_id" : ObjectId("573a123f55e195b227e7a78d"), "name" : "SomeName" }
This is obviously going to lead to lots of inconsistencies. I have tried various things. I've tried various methods such as findOneAndReplace, Update with Upsert, I've tried Replace, $setOnInsert. It all does the same thing when Upsert is involved it seems.
I have tried to use document[0] to access the first block of the array, but that throws an error... 'Unexpected Token ['
I have tried various methods and dug for hours through the various documentation, and have searched high and low for someone else having this problem, but it doesn't seem to be well documented issue for anyone else.
Anyone have any recommendations to make sure that all the fields are on the ROOT level, not nested under the variable name? Relevant code below.
findReplace: function(db, document, collection) {
var dbCollection = db.collection(collection);
var filter = document.name;
return new Promise(function(resolve, reject) {
dbCollection.updateOne({
"name" : filter
}, {
document
}, {
upsert: true
}, function(err, result) {
if (err) {
console.log('error', err)
reject(err);
}
console.log("Found Document and Upserted replacement Document");
resolve([{'status' : 'success'}]);
});
});
}
When you do this:
{
document
}
You are creating an object containing a document property and the variable's value as its value:
{
document: [ {"name" : "SomeName" } ]
}
This is new functionality from ES6. If you want to access the first item of the document variable, don't create a new object:
return new Promise(function(resolve, reject) {
dbCollection.updateOne({
"name" : filter
}, document[0], { // <========
upsert: true
}, function(err, result) {
if (err) {
console.log('error', err)
reject(err);
}
console.log("Found Document and Upserted replacement Document");
resolve([{'status' : 'success'}]);
});
});
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});
}
)
});
A piece of my Mongo document structure is:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "something",
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "bar", "rtype" : "Y", "name" : "Bill"}
], ...
I'm using Mongoose to access the data via the following model:
var Record = new Schema({
rdata: String,
rtype: String,
name: String
}, {_id: false});
var ThingSchema = new Schema({
Country: String,
Line : String,
Records : [Record],
Let's say I want to update the "Line" property of one of my documents, from being "Line" : "something" to "Line" : "way more interesting" by sending a PUT request to the appropriate API URL. I can see that the data being sent is all right. This is what the API does:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, updated);
});
});
};
The API comes back with 200/OK - but I see the following updated data:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "way more interesting", <-- updated correctly
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "foo", "rtype" : "X", "name" : "John"}
], ...
Notice, how the Records array got messed up by overwriting my second record by duplicating the first one. (If I switch on the automatic addition of '_id' to the subdocument by Mongoose, then even the "_id" fields will be the same on the two records within the array).
It may be relevant, that originally the Records were not added via Mongoose - but by importing a JSON document. Any suggestion as to how to start finding out why this is happening would be fantastic.
Try changing _.merge to _.extend, then call save directly on the thing document returned by the findById() method instead of the merged object updated:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
_.extend(thing, req.body);
thing.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, thing);
});
});
}
Another option is using the set method on the entity i.e. thing.set(req.body) before calling the save method on the thing object.
This answer by ShitalShah highlights the differences between merge and extend that is causing duplicates in your resulting object with merge but essentially:
Here's how extend/assign works: For each property in source, copy its
value as-is to destination. if property values themselves are objects,
there is no recursive traversal of their properties. Entire object
would be taken from source and set in to destination.
Here's how merge works: For each property in source, check if that
property is object itself. If it is then go down recursively and try
to map child object properties from source to destination. So
essentially we merge object hierarchy from source to destination.
While for extend/assign, it's simple one level copy of properties from
source to destination.
JSBin to illustrate the differences
I am writing an application using Mongo (using Mongo native driver), Node and Express.
I have Students, Courses and Professors documents in mongo.
I want to retrieve a list of all 'Professor' documents whose courses a student is currently taking or has taken in the past.
Students: {courseid, ....}
Course: {professors, ....}
Professors: {....}
This is what I intend on doing:
I first issue a query to retrieve all the course ids for a student.
Then I have to compose and issue another query to get the professor id for all those courses.
And then finally I have to get all the "professor" documents associated with the professor ids.
Step 1 is not a problem and now I have all the course ids. But I am not sure how to do step2. Step2 and 3 are similar, once I figure out step 2, step3 will be easy.
Basically I want to issue one query in step2 to retrieve all the professor ids. I don't want to issue 10 separate queries for 10 course ids.
Here is what I have:
function getProfsByStudent(req, res, next)
{
db.collection('students', function(err, stuColl)
{
stuId = new ObjectID.createFromHexString(req.params.stuId);
stuColl.find({_id : userId}, { 'current_course_id' : 1 , 'past_courses.course_id' : 1 , _id : 0 })
{
db.collection('courses', function(err, courseColl)
{
courseColl.find({$or : []}) // THIS IS WHERE I AM STUCK
});
res.send(posts);
});
});
}
Update
Question updated based on the answer.
So, this is the JSON I end up with after the stuColl.find call:
[{"current_course_id":"4f7fa4c37c06191111000005","past_courses":[{"course_id":"4f7fa4c37c06191111000003"},{"course_id":"4f7fa4c37c06191111000002"}]}]
Now I want to use the above to do another find to get all professor IDs. But all I get is a null result. I think I am very close. What am I doing wrong?
stuColl.find({_id : userId}, { 'current_course_id' : 1 , 'past_courses.course_id' : 1 , _id : 0 }).toArray(function(err, courseIdsArray)
{
db.collection('courses', function(err, courseColl)
{
courseColl.find({$or : [ {_id : 'courseIdsArray.current_courses_id' }, {_id : {$in : courseIdsArray.past_courses}} ]}, {'professor_ids' : 1}).toArray(function(err, professorIdsArray)
{
res.send(professorIdsArray);
});
});
});
I think that instead of $or you should be using the $in operator
http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24in
and:
stuColl.find....
should be returning its result toArray to be used in the $in operator.
Update
I think this is what you're looking for:
db.collection("Students").find({_id : "8397c60d-bd7c-4f94-a0f9-f9db2f14e8ea"}, {CurrentCourses:1, PastCourses : 1, _id : 0}).toArray(function(err, allCourses){
if(err){
//error handling
}
else{
var courses = allCourses[0].CurrentCourses.concat(allCourses[0].PastCourses);
db.collection("Courses").find({ _id : { $in: courses}}, {"Professors" : 1, _id : 0}).toArray(function(err, professors){
if(err){
//error handling
}
var allProfs = [];
for(var i = 0; i < professors.length; i++){
allProfs = allProfs.concat(professors[i].Professors);
}
db.collection("Professors").find({ _id : { $in: allProfs }}).toArray(function(err, results){
console.log(results);
});
});
}
});
It goes through the students collection and finds the student and then through all his/her courses to finally load all the teachers.