Mongoose update or insert many documents - node.js

I'm trying to use the latest version of mongoose to insert an array of objects, or update if a corresponding product id already exists. I can't for the life of me figure out the right method to use (bulkWrite, updateMany etc) and I can't can't seem to figure out the syntax without getting errors. For example, i'm trying
Product.update({}, products, { upsert : true, multi : true }, (err, docs) => console.log(docs))
this throws the error DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead. MongoError: '$set' is empty. You must specify a field like so: {$set: {<field>: ...}
and using updateMany just gives me the $set error. I can't seem to find any examples of people using this method, just wondering if somebody can provide me with an example of how to properly use this.
To be clear, I generate an array of objects, then I want to insert them into my db, or update the entry if it already exists. The field I want to match on is called pid. Any tips or advice would be greatly appreciated!
EDIT:
My product schema
const productSchema = new mongoose.Schema({
title : String,
image : String,
price_was : Number,
price_current : {
dollars : String,
cents : String
},
price_save_percent : String,
price_save_dollars : String,
price_save_endtime : String,
retailer : String
})
const Product = mongoose.model('Product', productSchema)
an example of the products array being passed
[
{
title: 'SOME PRODUCT',
image: '',
price_was: '139.99',
price_current: { dollars: '123', cents: '.49' },
price_save_percent: '12%',
price_save_dollars: '16.50',
price_save_endtime: null,
pid: 'VB78237321',
url: ''
},
{ ... },
{ ... }
]

You basically need bulkWrite operation
The array you want to update with
const products = [
{
title: 'SOME PRODUCT',
image: '',
price_was: '139.99',
price_current: { dollars: '123', cents: '.49' },
price_save_percent: '12%',
price_save_dollars: '16.50',
price_save_endtime: null,
pid: 'VB78237321',
url: ''
}
]
The query for bulk update
Model.bulkWrite(
products.map((product) =>
({
updateOne: {
filter: { retailer : product.pid },
update: { $set: product },
upsert: true
}
})
)
)

Related

MongoDB - update data in array of objects within object

I have a document in mongoDB structured like that
_id: ObjectId("generatedByMongo"),
name: {
required: true,
type: String,
trim: true
},
last: {
required: true,
type: String,
trim: true
},
grades: [{
grade: {
_id: ObjectId(""),
grade: Number,
date: date
}
}]
And to server I send array of objects containing 3 fields
[
{studentId}, {gradeId}, {newGrade}
]
What I'm trying to accomplish is I want to find in within that user collection grade with given gradeId and update it's value to newGrade. As far as I tried to do that I have done this
router.patch('/students/updateGrade',async(req,res) => {
const studentId = req.body.updateGradeArray[0].studentId;
const gradeId = req.body.updateGradeArray[0].gradeId;
const newGrade = req.body.updateGradeArray[0].newGrade;
try {
const student = await Student.find({_id: studentId})
.select({'grades': {$elemMatch: {_id: gradeId}}});
} catch(e) {
console.log(e);
}
}
);
If you intend to update just grade.grade(the number value), try this:
Student.updateOne(
// Find a document with _id matching the studentId
{ "_id": studentId },
// Update the student grade
{ $set: { "grades.$[selectedGrade].grade": newGrade } },
{ arrayFilters: [{ "selectedGrade._id": gradeId }] },
)
Why this should work:
Since you are trying to update a student document, you should be using one of MongoDB update methods not find. In the query above, I'm using the updateOne method. Inside the updateOne, I am using a combination of $set and $[identifier] update operators to update the student grade.
I hope this helps✌🏾

Mongoose how to auto add _id to objects in array within collection item?

i have a mongo collection that looks like this:
{
name: string
_id: (auto set)
items: array[
name: string
url: string
items: array[
{
name: string,
url: string,
items: []
}
]
]
}
I'm using findByIdAndUpdate (with mongoose) to add an item into the items array:
Menu.findByIdAndUpdate(
req.body.parentid,
{
$push: {
items: {
name: req.body.item.name,
url: req.body.item.url,
items: []
}
}
},
{
safe: true,
upsert: true,
new: true
},
function(err, model) {
if (err !== null) {
console.log(err);
}
}
);
This works fine, but it does not add an _id to each object inserted into the items array. And i really need an id for each one.
I'm guessing it comes from the method used, findByIdAndUpdate as it looks more like an update rather than an insert. If my thinking is correct.
Using mongodb 3.2.10 and mongoose 4.7.6.
Any help would be really appreciated.
Thanks.
EDIT: the _id: (auto set) is not real, it's being automatically added via mongo. But just at the top level objects.
Found the solution in this thread: mongoDB : Creating An ObjectId For Each New Child Added To The Array Field
basically, added
var ObjectID = require('mongodb').ObjectID;
and then forcing the creation:
$push: {
items: {
_id: new ObjectID(),
name: req.body.item.name,
url: req.body.item.url,
items: []
}
}
You dont need to sepcify _id: (auto set) in mongoose schema it will automatically add unique _id with each document.
if you don't define _id in Schema, mongoose automatically add a _id to array item.
for example:
const countrySchema = new Schema({
name: {
type: String
},
cities: [
{
// don't define _id here.
name: String
}
],
});
now when you insert a row, the result is something like this:
{name : 'Iran', cities : [{_id : 6202902b45f0d858ac141537,name :
'Tabriz'}]}

Which type to store an Array of MongoDB ObjectIds?

I want to save an Array of ObjectIds which I get via an REST interface via mongoose to a mongodb. I constantly run into the problem that I get a cast error when I save the objectsIds from the REST interface to the DB. The serverside code is written in typescript.
The schema is:
var WaSchema = new mongoose.Schema({
ownerId: { type: 'String', required: true },
options: { type: 'String', required: false },
launch: [{ type : 'ObjectId', required: true }],
});
From the REST interface I get for "lanch" an Array of strings: launch: Array<string>
Here is how I currently do the save:
WaModel.findOneAndUpdate(query, {
ownerId: userId,
options: wa.options,
launch: wa.launch
},
{ upsert: true },
(err, doc) => {
if (err) throw err
else return 'successfully saved/updated';
})
How must the ObjectId in the REST interface needs to look like to be correctly casted? Is it just a sting like '575e52790c0fc76a11e381d0' or does it need a prefix like ObjectId("575e52790c0fc76a11e381d0")?
How would the Array look like at the end? This depends a liitle on the answer of #1
I saw the populate function, can this be of help here?
1) If you are using mongoose then no need to add prefix ObjectId. You can save as an array of reference Ids.
var insertData = {
ownerId: userId,
options: wa.options,
launch: [ '56cea954d82cd11004ee67b5','56ceaa00d82cd11004ee67bc' ]
}
2) At the end your array will look like this.
"launch" : [
ObjectId("56cea954d82cd11004ee67b5"),
ObjectId("56ceaa00d82cd11004ee67bc")
],
3) And Yes, populate function will be helpful here. It will populate whole array. After populate it will look like
"launch" : [
{ _id: '56cea954d82cd11004ee67b5',
.... other fields
},
{ _id: '56ceaa00d82cd11004ee67bc',
.... other fields
},
]
I would say store the object ids in array with datatype ObjectId. So to do that you should convert the id in string format that is '575e52790c0fc76a11e381d0' into type ObjectId.
Use the following to convert it:
Consider the id is of a user (userId)
var stringUserId = '575e52790c0fc76a11e381d0';
var ObjectId = mongoose.Types.ObjectId;
var userId = ObjectId(stringUserId); //This should assign a value of datatype ObjectId to userId

How to find a record using dot notation & update the value in a schema using mongoose

I am using mongoose to perform CRUD operation on my db. This is how my model looks.
var EmployeeSchema = new Schema({
name: String,
description: {
type: String,
default: 'No description'
},
department: [],
lastUpdated: {
type: Date,
default: Date.now
}
});
The department can contains array of object like this.
[
{
"id" : "55ba28f680dec4383eeebf97",
"text" : "Sales",
"topParentId" : "55ba28f680dec4383eeebf8b",
"topParentText" : "XYZ"
},
{
"id" : "55ba28f680dec4383eeebf98",
"text" : "IT",
"topParentId" : "55ba28f680dec4383eeebf8b",
"topParentText" : "XYZ"
},
{
"id" : "55ba28f680dec4383eeebf94",
"text" : "Marketing",
"topParentId" : "55ba28f680dec4383eeebccc",
"topParentText" : "ABC"
}
]
Now I need to find all the employee where department.id = '55ba28f680dec4383eeebf94' and then I need to update the text of the object.
Employee.find({'department.id': '55ba28f680dec4383eeebf94'}, function(err, Employees) {
_.each(Employees, function (emp) {
_.each(emp.department, function (dept) {
if(dept.id === '55ba28f680dec4383eeebf94'){
dept.text = 'XXXXX'; // How to update the employee to save the updated text
}
});
});
});
What is the right way to save the employee with updated text for that department?
Iterating is code is not a "sharp" way to do this. It is better to use the MongoDB update operators, especially since there is no schema defined for the array items here, so no rules to worry about:
Employee.update(
{'department.id': '55ba28f680dec4383eeebf94'},
{ "$set": { "department.$.text": "XXXXX" },
function(err,numAffected) {
// handling in here
}
);
The $set is the important part, otherwise you overwrite the whole object. As is the positional $ operator in the statement, so only the matched ( queried item in the array ) index is updated.
Also see .find**AndUpdate() variants for a way to return the modified object.
I think you can use the update model:
Employee.update({department.id: '55ba28f680dec4383eeebf94'}, {department.text: 'XXXXX'}, {multi: true},
function(err, num) {
console.log("updated "+num);
}
);
First object is the query, what to find: {department.id: '55ba28f680dec4383eeebf94'}, the second one is the update, what to update: {department.text: 'XXXXX'} and the third one is the options to pass to the update, multi means update every records you find: {multi: true}

mongoose subdocument sorting

I have an article schema that has a subdocument comments which contains all the comments i got for this particular article.
What i want to do is select an article by id, populate its author field and also the author field in comments. Then sort the comments subdocument by date.
the article schema:
var articleSchema = new Schema({
title: { type: String, default: '', trim: true },
body: { type: String, default: '', trim: true },
author: { type: Schema.ObjectId, ref: 'User' },
comments: [{
body: { type: String, default: '' },
author: { type: Schema.ObjectId, ref: 'User' },
created_at: { type : Date, default : Date.now, get: getCreatedAtDate }
}],
tags: { type: [], get: getTags, set: setTags },
image: {
cdnUri: String,
files: []
},
created_at: { type : Date, default : Date.now, get: getCreatedAtDate }
});
static method on article schema: (i would love to sort the comments here, can i do that?)
load: function (id, cb) {
this.findOne({ _id: id })
.populate('author', 'email profile')
.populate('comments.author')
.exec(cb);
},
I have to sort it elsewhere:
exports.load = function (req, res, next, id) {
var User = require('../models/User');
Article.load(id, function (err, article) {
var sorted = article.toObject({ getters: true });
sorted.comments = _.sortBy(sorted.comments, 'created_at').reverse();
req.article = sorted;
next();
});
};
I call toObject to convert the document to javascript object, i can keep my getters / virtuals, but what about methods??
Anyways, i do the sorting logic on the plain object and done.
I am quite sure there is a lot better way of doing this, please let me know.
I could have written this out as a few things, but on consideration "getting the mongoose objects back" seems to be the main consideration.
So there are various things you "could" do. But since you are "populating references" into an Object and then wanting to alter the order of objects in an array there really is only one way to fix this once and for all.
Fix the data in order as you create it
If you want your "comments" array sorted by the date they are "created_at" this even breaks down into multiple possibilities:
It "should" have been added to in "insertion" order, so the "latest" is last as you note, but you can also "modify" this in recent ( past couple of years now ) versions of MongoDB with $position as a modifier to $push :
Article.update(
{ "_id": articleId },
{
"$push": { "comments": { "$each": [newComment], "$position": 0 } }
},
function(err,result) {
// other work in here
}
);
This "prepends" the array element to the existing array at the "first" (0) index so it is always at the front.
Failing using "positional" updates for logical reasons or just where you "want to be sure", then there has been around for an even "longer" time the $sort modifier to $push :
Article.update(
{ "_id": articleId },
{
"$push": {
"comments": {
"$each": [newComment],
"$sort": { "$created_at": -1 }
}
}
},
function(err,result) {
// other work in here
}
);
And that will "sort" on the property of the array elements documents that contains the specified value on each modification. You can even do:
Article.update(
{ },
{
"$push": {
"comments": {
"$each": [],
"$sort": { "$created_at": -1 }
}
}
},
{ "multi": true },
function(err,result) {
// other work in here
}
);
And that will sort every "comments" array in your entire collection by the specified field in one hit.
Other solutions are possible using either .aggregate() to sort the array and/or "re-casting" to mongoose objects after you have done that operation or after doing your own .sort() on the plain object.
Both of these really involve creating a separate model object and "schema" with the embedded items including the "referenced" information. So you could work upon those lines, but it seems to be unnecessary overhead when you could just sort the data to you "most needed" means in the first place.
The alternate is to make sure that fields like "virtuals" always "serialize" into an object format with .toObject() on call and just live with the fact that all the methods are gone now and work with the properties as presented.
The last is a "sane" approach, but if what you typically use is "created_at" order, then it makes much more sense to "store" your data that way with every operation so when you "retrieve" it, it stays in the order that you are going to use.
You could also use JavaScript's native Array sort method after you've retrieved and populated the results:
// Convert the mongoose doc into a 'vanilla' Array:
const articles = yourArticleDocs.toObject();
articles.comments.sort((a, b) => {
const aDate = new Date(a.updated_at);
const bDate = new Date(b.updated_at);
if (aDate < bDate) return -1;
if (aDate > bDate) return 1;
return 0;
});
As of the current release of MongoDB you must sort the array after database retrieval. But this is easy to do in one line using _.sortBy() from Lodash.
https://lodash.com/docs/4.17.15#sortBy
comments = _.sortBy(sorted.comments, 'created_at').reverse();

Resources