mongoose - findOneAndUpdate with $set flag - node.js

Consider this command:
WorkPlan.findOneAndUpdate({ _id: req.params.id }, updateObj, function(err) {
...
})
versus this:
WorkPlan.findOneAndUpdate({ _id: req.params.id }, { '$set': updateObj }, function(err) {
...
})
While developing my project, I was surprised to find out that the result of the first command is the same as the result of the second command: the updateObj is merged into the existing record in the database, even in the first case when it is supposed to replace it. Is this a bug in mongoose/mongodb or am I doing something wrong? how can I replace an object on update instead of merging it? I'm using mongoose 4.0.7.
Thanks.
==========
Update:
This is the actual WorkPlan schema definition:
workPlanSchema = mongoose.Schema({
planId: { type: String, required: true },
projectName: { type: String, required: true },
projectNumber: { type: String, required: false },
projectManagerName: { type: String, required: true },
clientPhoneNumber: { type: String, required: false },
clientEmail: { type: String, required: true },
projectEndShowDate: { type: Date, required: true },
segmentationsToDisplay: { type: [String], required: false },
areas: [
{
fatherArea: { type: mongoose.Schema.ObjectId, ref: 'Area' },
childAreas: [{ childId : { type: mongoose.Schema.ObjectId, ref: 'Area' }, status: { type: String, default: 'none' } }]
}
],
logoPositions: [
{
lat: { type: Number, required: true },
lng: { type: Number, required: true }
}
],
logoPath: { type: String, required: false },
}, { collection: 'workPlans' });
WorkPlan = mongoose.model('WorkPlan', workPlanSchema);
And this is an example of updateObj:
var updateObj = {
projectManagerName: projectManagerName,
clientEmail: clientEmail,
clientPhoneNumber: clientPhoneNumber,
segmentationsToDisplay: segmentationsToDisplay ? segmentationsToDisplay.split(',') : []
}
Therefore, when I'm NOT using the $set flag, I would expect the field projectNumber, for example, not to exist in the new record, yet I see it is still there.

Mongoose update treats all top level keys as $set operations (this is made more clear in the older docs: Mongoose 2.7.x update docs).
In order to get the behavior you want, you need to set the overwrite option to true:
WorkPlan.findOneAndUpdate({ _id: req.params.id }, updateObj, { overwrite: true }, function(err) {
...
})
See Mongoose Update documentation

In addition to the answer above:
[options.overwrite=false] «Boolean» By default, if you don't include
any update operators in doc, Mongoose will wrap doc in $set for you.
This prevents you from accidentally overwriting the document. This
option tells Mongoose to skip adding $set.
Link to docs: https://mongoosejs.com/docs/api.html#model_Model.update

This is works for me $set in Mongoose 5.10.1,
WorkPlan.where({ _id: req.params.id }).updateOne(updateObj);
Note:if you have inner object then give exact path of each key in updateObj
example:
"Document.data.age" = 19
ref: https://mongoosejs.com/docs/api.html#query_Query-set

Related

How to overwrite one field of a schema with another in mongodb

how can I overwrite the value of officialLyric with the value of updateLyric??
artist: { type: String, required: true },
title: { type: String, required: true },
officialLyric: { type: String, required: true },
editedLyrics: [
{
updatedLyric: String,
status: {
type: String,
enum: ["Aproved", "Rejected", "Pending"],
default: "Pending",
},
userId: { type: Schema.Types.ObjectId, required: true, ref: "User" },
},
],
releaseDate: { type: Date },
see image for clear depiction of the question.
enter image description here
You can try update with aggregation pipeline starting from MongoDB 4.2,
$arrayElemAt to get first value of updatedLyric from editedLyrics array and update it into officialLyric
db.collection.updateMany(
{}, // put your query
[{
$set: {
officialLyric: {
$arrayElemAt: ["$editedLyrics.updatedLyric", 0]
}
}
}]
)
Playground
If you want to always have to value of latest updateLyric in editedLyric array in officialLyric, you don't need to actually store officialLyric in DB. you can use mongoose virtual fields and remove officialLyric from schema.
LyricSchema.virtual('officialLyric').get(function () {
if(!this.editedLyrics.length) return null;
return this.editedLyrics[this.editedLyrics.length-1].updatedLyric;
});
If you still want to store the officialLyric first and then overwrite it with edited version you save. You can use hooks.
LyricSchema.post('save', async(error, doc, next) => {
if(doc.editedLyrics.length && doc.officialLyric != doc.editedLyrics[doc.editedLyrics.length-1].updatedLyric){
doc.officialLyric = doc.editedLyrics[doc.editedLyrics.length-1].updatedLyric;
await doc.save();
}
next();
});

How to limit one field only in mongoose

I am creating something like fb... I want to show 3 comments only on home page... How to limit one field only... my schema is this:
const postSchema = new Schema({
admin: { type: Types.ObjectId, ref: 'users', required: true },
text: { type: String, required: true },
comments: [{
postId: { type: Types.ObjectId, ref: 'posts', required: true },
admin: { type: Types.ObjectId, ref: 'users', required: true },
comment: { type: String, required: true },
time: { type: Date, default: Date.now }
}],
createdAt: { type: Date, default: Date.now },
modified: { type: Boolean, default: false }
});
I have all comments in an array... I want to limit them... Please help
Try using $slice (projection) in MongoDB. The $slice operator controls the number of items of an array that a query returns.
If you want to fetch first 3 comments only then your query will be as below:
db.slice.find( {}, { comments: { $slice: 3 } } )
In case if you want last 3 comments then your query will be:
db.slice.find( {}, { comments: { $slice: -3 } } )

Getting CoreMongooseArray instead of normal array

I have this schema:
var orderSchema = new mongoose.Schema({
history: [{
"type": {
type: String,
enum: [
'ORDER_HISTORY_DRIVER_DETAILS',
'ORDER_HISTORY_LOADING',
'ORDER_HISTORY_LOCATION',
'ORDER_HISTORY_UNLOADING'
],
required: true
},
date: {
type: Date
},
state: {
type: String,
enum: [
'ORDER_HISTORY_STEP_STATE_COMPLETED',
'ORDER_HISTORY_STEP_STATE_CURRENT',
'ORDER_HISTORY_STEP_STATE_FUTURE',
],
default: 'ORDER_HISTORY_STEP_STATE_FUTURE',
required: true
}
}]
})
At one point, I need to remove all subdocuments that have a type of "ORDER_HISTORY_LOCATION", so I'm running this:
let result = await Order.findOneAndUpdate(
{orderId: req.params.orderId},
{
$pull: {
history: {type: "ORDER_HISTORY_LOCATION"}
}
}, {new: true}
);
When i log "result.history" i get this:
CoreMongooseArray [
{ state: 'ORDER_HISTORY_STEP_STATE_CURRENT',
_id: 5caf8a41641e6717d835483d,
type: 'ORDER_HISTORY_DRIVER_DETAILS' },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d835483c,
type: 'ORDER_HISTORY_LOADING',
date: 2019-05-08T09:00:00.000Z },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d835483b,
type: 'ORDER_HISTORY_LOADING',
date: 2019-05-09T09:00:00.000Z },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d8354837,
type: 'ORDER_HISTORY_UNLOADING',
date: 2019-05-13T09:00:00.000Z } ]
What is this "CoreMongooseArray"? I can't do anything with it. I also can't find any documentation on it.
CoreMongooseArray seems to be inheriting the Array type and has almost the same behavior.
Source code (at the time of writting) : https://github.com/Automattic/mongoose/blob/3e523631daa48a910b5335c747b3e5d080966e6d/lib/types/core_array.js
In case you want to convert it to a simple array, just do this :
const history = Array.from(...result.history)
Beware, if this array contains objects, each object will have undesirable additional Mongoose properties, as they are Mongoose schemas documents. You will need to convert them into plain JavaScript objects :
const history = Array.from(...result.history).map(v => v.toJSON())
Hope it helps.
This worked for me!
const history = Array.from([...result.history])

Find all the documents that has same subdocuments mongoose

I have postSchema which references the tagsSchema.
var tagsSchem = new Schema({
name: {
type: String,
required: true
}
}, {
timestamps: true
});
// create a schema
var postsSchema = new Schema({
title: {
type: String,
required: true,
unique: true
},
mainImage: {
type: String
},
category: {
type: String,
required: true
},
body: {
type: String,
required: true
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
tags: [tagsSchem]
}, {
timestamps: true
});
One post can contain any no. of tags. So if a post has 3 tags then I want to get all the posts with those 3 tags without querying it multiple times. Is it possible?
When you perform find, you can use the $in option to find values that are in your array. For example:
posts.find({tags:{$in:{["tag1","tag2","tag3"]}}, function(err,data) {
... //Your code here
}
This will take all the posts that contains one of the three tags. It's important you have to pass an array in the $in option. This should work.

$inc not working for my mongo database (on localhost)

This should be a simple problem. I've formatted my 'update' exactly how it is on mongo's documentation, as you can see:
Message.update(
{ id: messageID },
{ $inc: { votes: 1 } }
);
I am trying to increase 'votes' by 1. Yet it is not changing. Here is my schema file:
var mongoose = require('mongoose');
var MessageSchema = new mongoose.Schema({
room: {
type: String,
required: true
},
timestamp: {
type: Object,
required: true
},
votes: {
type: Number
},
id: {
type: String,
required: true
},
uid: {
type: String,
required: true
},
parent: {
type: String,
required: true
},
text: {
type: String,
required: true
}
});
module.exports = mongoose.model('messages', MessageSchema);
The input 'messageID' is correct, as the 'find' method works perfectly well with this table.
Try with findByIdAndUpdate instead of update . Hope it works
Message.findByIdAndUpdate(messageID ,{ $inc: { votes: 1 }},function(err, response) {
res.send(response);
});

Resources