mongodb, check if the key exist while appending to an array - node.js

Note: checking if the key books exist or not, creating if not and than updating it.
I am using mongodb driver with nodejs.
In the db.collection('userData')The document looks like this:
{
user_id: 'user1',
books: [{
id: 'book1',
title: 'this is book1'
},
{
id: 'book1',
title: 'this is book1'
}]
}
when inserting a new book entry, how to check if the array of books exists in the document, if not then add a key books in the document and then insert the book entry.

You have to do 2 separate queries,
Find user document
Check condition if books field present
If Present then push object, else set new field
var user_id = "user1";
var bookData = { id: 'book1', title: 'this is book1' };
// FIND USER DATA
var userData = await db.collection('userData').findOne({ user_id: user_id }, { books: 1 });
var updateBody = { $push: { books: bookData } };
// IF BOOKS FIELD NOT PRESENT THEN SET NEW
if (!userData.books) {
updateBody = { $set: { books: [bookData] } };
}
var updateData = await db.collection('userData').updateOne({ user_id: user_id }, updateBody);
console.log(updateData);
Second option you can use update with aggregation pipeline starting from MongoDB 4.2,
$ifNull check is field is null then return []
$concatArrays to concat current books with new book object
var bookData = { id: 'book1', title: 'this is book1' };
db.collection('userData').update({
// put your condition
},
[{
$set: {
books: {
$concatArrays: [
{ $ifNull: ["$books", []] },
[bookData]
]
}
}
}],
{ multi: true }
);
Playground

Related

For each MongoDB document, add a new variable that increments

I have a collection with the following documents:
[{_id: abc, name: "foo"}, {_id: def, name: "bar"}, {_id: ghi, name: "baz"}]
I want to change every document in that collection so it has a new field, which is unique, and that has a letter and a number, the number increases with each document. So I want to have this:
[{_id: abc, name: "foo", customId: "m1"}, {_id: def, name: "bar", customId: "m2"}, {_id: ghi, name: "baz", customId: "m3"}]
I tried using the most voted answer in this question, but it only has a number which is kind of the index in the array, but I want a letter and the number next to it.
I am using NodeJS and Express with the mongoose package. I don't mind if the answer is either using javascript code or a mongo cli command.
Any help is very appreciated, thanks in advance.
I'm assuming you need to update the existing table and also need to create the counter field for the upcoming data's,
function update() { //updating existing table
user.aggregate(
[{
$match: {
"counter": { $exists: false }
}
}],
function (err, res) {
if (err) {
console.log(err)
}
var i = 0;
var newId;
res.forEach((element, index) => {
i = i + 1;
newId = "count" + i
user.update(
{ id: element.id },
{ $set: { "Counter": newId } }
);
});
})
}
function create(userparam) {//while creating new table
autonumber.find({}, function (err, res) {
let counter_value = "Count" + res[0].incrementer
//assuming incrementer to be feild in autonumber table
const user = new User(userparam);
user.Counter = counter_value;
return await user.save()
})
}
I'm beginner,so if this code is inefficient or wrong .... sorry in advance.

Mongoose save an array of objects in Ref schema

I've got the Parent Schema:
const parentSchema = new Schema({
name: {
type: String,
},
children: [{
type: Schema.Types.ObjectId,
ref: "Children"
}]
})
And this is the Children Schema:
const childrenSchema = Schema({
name: {
type: String
},
surname: {
type: String
}
})
I have an incoming user register POST request in the following format:
{
"name": "TEST",
"children" : [
{ "name":"test","surname": "test" },
{ "name":"test","surname": "test" }
]
}
Here's the router:
router.post("/register", (req, res, next) => {
const {name, children} = req.body;
let newParent = newParent({
name,
children
});
newParent.save((err, result) => {
// res.send(result) etc.
})
}
This results in the following error:
Cast to Array failed for value "[ { name: 'test', surname: 'test' } ]" at path "children"
How can I save all children and keep in the ref only the children _id so i can later populate the Parent collection?
The children field in the parent is expecting an arrays of ObjectIds but you are passing it an arrays of objects that do not conform to that expectation. Please try saving the children, getting the ids and then using those ids to populate the children field in parent document. Something like below:
children.save()
.then(results => {
childrenids = []
results.foreach[item => childrenids.push(result._id)]
newParent.children = chilrenids
newParent.save()
.then(results => res.send({results})
})
To save childData in Parents, You need to save first child's data in children schema Then get childIds and save to Parent Data.
Working Example:
let req = {
"name" : "TEST",
"children" : [
{ "name":"test","surname": "test" },
{ "name":"test","surname": "test" }
]
}
Children.collection.insert(req.children, function (err, docs) {
if (err){
conasolw.log(err);
} else {
var ids = docs.ops.map(doc=>{ return doc._id});;
console.log(ids);
let newParent = Parent({
name : req.name,
children : ids
});
newParent.save((err, result) => {
console.log('parent save');
console.log(err);
console.log(result);
})
}
});
Note :
Test on "mongoose": "^5.3.3"

MongoDB update subdocument changes its _id

I have the next document, and I want to update the addresses with id of 21, change the alias to 'Family'. I run User.update({ _id: 2, 'addresses._id': 21 }, { 'addresses.$': newAddress });
Which works fine, with an annoying side effect, is that Mongo generates a new id for the subdocument. Is there any way to update a subdocument without getting a new id?
'user': {
'_id': 2,
'addresses': [
{
'_id': '20',
'alias': 'Work',
'postal_code': 1235
},
{
'_id': '21',
'alias': 'Home',
'postal_code': 1235
}
]
}
I already solved this using
User.update(
{ _id: req.user._id, 'addresses._id': addressId },
{ $set: {
'addresses.$.alias': newAddress.alias,
'addresses.$.address': newAddress.address,
'addresses.$.areaId': newAddress.areaId,
'addresses.$.cityId': newAddress.cityId,
'addresses.$.postal_code': newAddress.postal_code
} }
);
This doesn't change the id of the subdocument, but I don't like this solution for obvious reasons.
Adding to JasonCust's answer, the correct approach is to inject the old id into the new address to keep the id unchanged and avoid having to enter each individual key.
Below is an example:
const userSchema = new Schema({
addresses: [{ country: String }]
});
const User = mongoose.model('User', userSchema);
const user = await User.create({
addresses: [
{ country: 'Egypt' }
]
});
const oldAddressId = user.addresses[0]._id;
//
const newAddress = {
_id: oldAddressId,
country: 'Japan'
};
await User.updateOne({ 'addresses._id': oldAddressId }, { 'addresses.$': newAddress });
const userAfterUpdate = await User.findOne({ _id: user._id });
assert.equal(userAfterUpdate.addresses[0].country, 'Japan');
assert.equal(userAfterUpdate.addresses[0]._id.toString(), oldAddressId.toString());
The short answer is: no. In essence the update object { 'addresses.$': newAddress } is a command to replace the entire object at the matched pointer location with the newAddress object. That said, if the newAddress object includes the _id value then it should be stored as the value.

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'}]}

Get result as an array instead of documents in mongodb for an attribute

I have a User collection with schema
{
name: String,
books: [
id: { type: Schema.Types.ObjectId, ref: 'Book' } ,
name: String
]
}
Is it possible to get an array of book ids instead of object?
something like:
["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]
Or
{ids: ["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]}
and not
{
_id: ObjectId("53eb79d863ff0e8229b97448"),
books:[
{"id" : ObjectId("53eb797a63ff0e8229b4aca1") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acac") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acad") }
]
}
Currently I am doing
User.findOne({}, {"books.id":1} ,function(err, result){
var bookIds = [];
result.books.forEach(function(book){
bookIds.push(book.id);
});
});
Is there any better way?
It could be easily done with Aggregation Pipeline, using $unwind and $group.
db.users.aggregate({
$unwind: '$books'
}, {
$group: {
_id: 'books',
ids: { $addToSet: '$books.id' }
}
})
the same operation using mongoose Model.aggregate() method:
User.aggregate().unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
Note that books here is not a mongoose document, but a plain js object.
You can also add $match to select some part of users collection to run this aggregation query on.
For example, you may select only one particular user:
User.aggregate().match({
_id: uid
}).unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
But if you're not interested in aggregating books from different users into single array, it's best to do it without using $group and $unwind:
User.aggregate().match({
_id: uid
}).project({
_id: 0,
ids: '$books.id'
}).exec(function(err, users) {
// use users[0].ids
})

Resources