Insert default values not working mongodb - mongodb

I am using mongoose version 5.2.5 and here is the sample model of my order
....
let PlaceOrderSchema = new mongoose.Schema({
any: {}
}, { strict: false },
{ timestamps: { updatedAt: 'last_updated', createdAt: 'created' });
I am using the above model in main script with mongoose save and findOneAndUpdate.
In our production system , we are seeing many document that does not have last_updated key missing in the save document.
Here are sample code of the mongoose save method and findOneAndUpdate in our main script.We are seeing some of the documents have updated_at keys while very few of them does not have it while saving the document
let orderModel = require('./orderModel');
let newOrder = {
order_no: 1234
};
//save usage code
(new Order(newOrder).save({lean: true}, ()=> {
//do...something
}));
//findOneAndUpdate usage Code
let orderNo = 123
OrderModel.findOneAndUpdate({ order_no: orderNo },
{
$set: { items: [{product_id: 'abc', quantity: 1}] },
},
{ new: true, upsert: true },
(err, res) => {
//do_something
});
Can any one share why we have few documents are getting saved without updated_at?

You need to use option setDefaultsOnInsert: true during the update operation.
Docs
By default, mongoose only applies defaults when you create a new
document. It will not set defaults if you use update() and
findOneAndUpdate(). However, mongoose 4.x lets you opt-in to this
behavior using the setDefaultsOnInsert option.
OrderModel.findOneAndUpdate(
{ order_no: orderNo },
{ $set: { items: [{ product_id: "abc", quantity: 1 }] }},
{ new: true, upsert: true, setDefaultsOnInsert: true }
)

Related

Mongo How to use select to return multiple selected properties from document?

I am using findOneAndUpdate, where I want
to return updated document
i dont want to return the entire document but only the following:
one object out of an array + a virtual property in the document.
const notifications = {
to:
messages: [
{_id: "23452", title:"hello"}, {_id: "23452", title:"bye"}
]
...
}
so for example I would want to only return the object {_id: "23452", title:"bye"} AND unreadCount virtual field prop.
my code works so far as I am returning updated document and only the message I want, but I dont know how to return also the unreadCount prop.
schema:
const notificationSchema = new mongoose.Schema({
to: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
messages: [{
title: {
type: String,
required: true
},
isRead: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: new Date()
}
}, ]
},
{timestamps: true, toObject: {virtuals: true}
});
notificationSchema.virtual('unreadCount').get(function() {
... return count;...
})
updateRead: async (userId, id) => {
const notification = await Notification.findOneAndUpdate({to: userId, 'messages._id': id}, {
$set: { "messages.$.isRead": true} },
{ select: {
messages: {
$elemMatch: {_id: id}
}
}, new: true});
}

Editing specific element of an array of a particular collection - mongoDb

I am new to mongoDb. I have created a collection named task that has comments field which is array along with other fields. I need to edit specific comment of the task. There is a edit button in each comment. Both task id and comment id are available. Now how to edit specific comment of the task?
Thanks in advance
task api
{
"status":true,
"task":[
{
"_id":"61dfef323a6ee474c4eba926",
"description":"hello there",
"title":"hello",
"comments":[
{
"comment_id":1,
"username":"test",
"comment":"abcd",
"status":true,
},
{
"comment_id":2,
"username":"test",
"comment":"abcdsdfsdf",
"status":true,
}
],
"createdAt":"2022-01-13T09:21:54.795Z",
"updatedAt":"2022-01-13T09:21:54.795Z",
"__v":0
}
]
}
Task model schema
const taskSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
comments: [Object],
}, {
timestamps: true,
});
I tried using $set but I don't know how to use it in the inner array.
router.route('./comments/edit').post((req, res) => {
const commentId = req.body.commentId;
const taskId = req.body.postId;
const comment = req.body.editedComment;
const updatedAt = new Date();
Task.updateOne(
{ _id: taskId},
{
//what to do here?
// $set: { comments: [ {'comment_id': commentId} ]},
}
)
.then((response) => res.json({ status: true, msg: 'Comment Edited!' }))
.catch(err => res.json({ status: false, msg: err }));
});
Thanks in advance.
This is how to do best:
db.collection.update({
status: true
},
{
$set: {
"task.$[x].comments.$[y].username": "New Name"
}
},
{
arrayFilters: [
{
"x._id": "61dfef323a6ee474c4eba926"
},
{
"y.comment_id": 2
}
]
})
Explained:
Define x and y as arrayFIlters in the update statement.
In the $set statement provide the x & y filters to identify the specific comment for update.
In the example I update the username , but you can update any other value from the targeted array subelement addressed by x & y.
playground
And here is how to update two values at same time in the same nested array element.

Update a value on each document with a value existing on that document

Objective:
I would like to update each document's points_left with the document's max_points value.
Player.js (Schema)
import mongoose from 'mongoose';
let Schema = mongoose.Schema;
let PlayerSchema = new Schema({
player_id: {
type: String,
required: true
},
points_left: {
type: Number,
default: 0
},
max_points: {
type: Number,
default: 5
},
created_date:{
type: Date,
default: Date.now
},
});
let Player = mongoose.model("players", PlayerSchema);
export default Player;
cron.js (cron job that plays every 24 hours)
/** This is not actually updating **/
Player.updateMany(
{},
[{"$set": { points_left: "$max_points" }}]
)
The below query works on MongoDB via terminal.
db.players.update(
{},
[{"$set": {points_left: "$max_points"}}],
{ multi : true }
)
Expected: {player_id: 1, points_left: 5, max_points: 5, created_date: 1234567890}
Actual: {player_id: 1, points_left: 0, max_points: 5, created_date: 1234567890}
If your using an async function make sure you are using await before calling updateMany
await Player.updateMany(
{},
[{"$set": { points_left: "$max_points" }}]
);
If not execute the query or it will not run, .then() will also execute if you need a callback.
Player.updateMany(
{},
[{"$set": { points_left: "$max_points" }}]
).exec();
I found a work-around. This one cycles through all of the documents and saves the points_left to max_points
Player.find({})
.then( docs => {
docs.forEach(doc => {
doc.points_left = doc.max_points
doc.save();
})
});
```

Mongoose pull ObjectId from array

i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};

auto increment ids in mongoose

How do I have autoincrement ids in mongoose? I want my ids to start like 1, 2, 3, 4, not the weird id numbers mongodb creates for you?
Here's my schema:
var PortfolioSchema = mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Use mongoose-auto-increment:
https://github.com/codetunnel/mongoose-auto-increment
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
var connection = ....;
autoIncrement.initialize(connection);
var PortfolioSchema = new mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio' });
module.exports = mongoose.model('Portfolio', PortfolioSchema);
Or if you prefer to use an additional field instead of overriding _id, just add the field and list it in the auto-increment initialization:
var PortfolioSchema = new mongoose.Schema({
portfolioId: {type: Number, required: true},
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio', field: 'portfolioId' });
If you want to have a incrementing numeric value in _id then the basic process is you are going to need something to return that value from a store somewhere. One way to do this is use MongoDB itself to store data that holds the counters for the _id values for each collection, which is described within the manual itself under Create and Auto-Incrementing Sequence Field.
Then as you create each new item, you use the implemented function to get that "counter" value, and use it as the _id in your document.
When overriding the default behavior here, mongoose requires that you both specify the _id and it's type explicitly with something like _id: Number and also that you tell it to no longer automatically try to supply an ObjectId type with { "_id": false } as an option on the schema.
Here's a working example in practice:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var counterSchema = new Schema({
"_id": String,
"counter": { "type": Number, "default": 1 }
},{ "_id": false });
counterSchema.statics.getNewId = function(key,callback) {
return this.findByIdAndUpdate(key,
{ "$inc": { "counter": 1 } },
{ "upsert": true, "new": true },
callback
);
};
var sampleSchema = new Schema({
"_id": Number,
"name": String
},{ "_id": false });
var Counter = mongoose.model( 'Counter', counterSchema ),
ModelA = mongoose.model( 'ModelA', sampleSchema ),
ModelB = mongoose.model( 'ModelB', sampleSchema );
async.series(
[
function(callback) {
async.each([Counter,ModelA,ModelB],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
async.eachSeries(
[
{ "model": "ModelA", "name": "bill" },
{ "model": "ModelB", "name": "apple" },
{ "model": "ModelA", "name": "ted" },
{ "model": "ModelB", "name": "oranage" }
],
function(item,callback) {
async.waterfall(
[
function(callback) {
Counter.getNewId(item.model,callback);
},
function(counter,callback) {
mongoose.model(item.model).findByIdAndUpdate(
counter.counter,
{ "$set": { "name": item.name } },
{ "upsert": true, "new": true },
function(err,doc) {
console.log(doc);
callback(err);
}
);
}
],
callback
);
},
callback
);
},
function(callback) {
Counter.find().exec(function(err,result) {
console.log(result);
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
For convience this implements a static method on the model as .getNewId() which just descriptively wraps the main function used in .findByIdAndUpdate(). This is a form of .findAndModify() as mentioned in the manual page section.
The purpose of this is that it is going to look up a specific "key" ( actually again the _id ) in the Counter model collection and perform an operation to both "increment" the counter value for that key and return the modified document. This is also aided with the "upsert" option, since if no document yet exists for the requested "key", then it will be created, otherwise the value will be incremented via $inc, and it always is so the default will be 1.
The example here shows that two counters are being maintained independently:
{ _id: 1, name: 'bill', __v: 0 }
{ _id: 1, name: 'apple', __v: 0 }
{ _id: 2, name: 'ted', __v: 0 }
{ _id: 2, name: 'oranage', __v: 0 }
[ { _id: 'ModelA', __v: 0, counter: 2 },
{ _id: 'ModelB', __v: 0, counter: 2 } ]
First listing out each document as it is created and then displaying the end state of the "counters" collection which holds the last used values for each key that was requested.
Also note those "weird numbers" serves a specific purpose of always being guranteed to be unique and also always increasing in order. And note that they do so without requiring another trip to the database in order to safely store and use an incremented number. So that should be well worth considering.