One query to find every child with one ref - javascript

Today i'm in front of a problem. I'm using express, mongodb with mongoose to handle the back-end of my application.
I got one model with a ObjectId reference to his parent. I want to retrieve every documents that contain this parent. But i'm receiving only the name of the parent not the id.
The only solution that i found it's to do a first query to find the id then one more to find my documents. I want to know if that's possible to do that in one query ?
My Model :
const childSchema = new Schema({
name: {
type: String,
required: true
},
_exampleParent: {
type: Schema.Types.ObjectId, ref: 'parents',
}
});
My query :
Parent.findOne({name:req.query.parent}, function(err, values){
if(err) return next(err);
Child.find({_exampleParent:values.id},
'name',
function(err, values){
if(err) return next(err);
res.send(values);
}
);
});
Thanks guys !

You can populate "child" and filter with parent's name afterwards.

You Can Do This, By Changing The Schema:
Pass Reference Of Child To Parent Instead of parent reference to the child.
Below I am just showing an example of how to do this, do consider your variable names as mine can differ:
var parentSchema = new Schema({name:{
type:'string'
},
children:[{type:Schema.Types.ObjectId,
ref:"child"}]
});
var childSchema = new Schema({name:{type:'string'}});?
var child = mongoose.model('child',childSchema);
var parent = mongoose.model('parent',parentSchema);
Now To Retrieve:
parent.findOne({name:req.query.name}).populate('child').then((result)=>{
console.log(result.children) //will be an array of all the children having the same parent.
})

Related

How to delete a sub-document of a document completely from the mongo database

I'm trying to delete a mongodb object and then once deleted, I want to delete everything associated with that mongodb object. Including nested mongodb objects from my mongo database.
var parentObjectSchema = new mongoose.Schema({
name: String,
split: Number,
parts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "ChildObjectSchema"
}
],
});
var childObjectSchema = new mongoose.Schema({
name: String,
number: Number,
things: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Things"
}
],
});
So I am trying to delete the parentObject, and childObjects that come along with it. Not sure how I would go about doing that. I am successful in deleting the parentObject but that childObject is still in the mongodb, taking up space. Any ideas?
MongoDB doesn't provide the notion of foreign keys like other databases do. Mongoose has convenience methods in the client library that populates your documents with other documents using multiple queries and joining the results:
https://mongoosejs.com/docs/populate.html
If you want to do a cascading deletion then you'll need to grab the object ids of the children in the parent documents you want to delete, and then execute a delete against those children documents.
Here's a simplified example:
const deleteThing = (thingId) => {
thingObjectSchema.remove({ _id: thingId });
};
const deleteChild = (childId) => {
childObjectSchema.findOne({ _id: childId }).select('things').lean().exec((err, child) => {
for (const thingId of child.things) {
deleteThing(thingId);
}
childObjectSchema.remove({ _id: childId });
})
};
const deleteParent = (parentId) => {
parentObjectSchema.findOne({ _id: parentId }).select('parts').lean().exec((err, parent) => {
for (const childId of parent.parts) {
deleteChild(childId);
}
parentObjectSchema.remove({ _id: parentId });
})
};
// note: not actually tested

Update Array attribute using Mongoose

I am working on a MEAN stack application in which i defined a model using following schema:
var mappingSchema = new mongoose.Schema({
MainName: String,
Addr: String,
Mapping1: [Schema1],
Mappings2: [Schema2]
},
{collection : 'Mappings'}
);
I am displaying all this data on UI and Mapping1 & Mapping2 are displayed in the 2 tables where I can edit the values. What I am trying to do is once I update the values in table I should update them in database. I wrote put() api where I am getting these two updated mappings in the form of object but not able to update it in database. I tried using findAndModify() & findOneAndUpdate() but failed.
Here are the Schema1 & Schema2:
const Schema1 = new mongoose.Schema({
Name: String,
Variable: String
});
const Schema2 = new mongoose.Schema({
SName: String,
Provider: String
});
and my put api:
.put(function(req, res){
var query = {MainName: req.params.mainname};
var mapp = {Mapping1: req.params.mapping1, Mapping2: req.params.mapping2};
Mappings.findOneAndUpdate(
query,
{$set:mapp},
{},
function(err, object) {
if (err){
console.warn(err.message); // returns error if no matching object found
}else{
console.log(object);
}
});
});
Please suggest the best to way update those two arrays.
UPDATE :
I tried this
var mapp = {'Mapping2': req.params.mapping2};
Mappings.update( query ,
mapp ,
{ },
function (err, object) {
if (err || !object) {
console.log(err);
res.json({
status: 400,
message: "Unable to update" + err
});
} else {
return res.json(object);
}
});
what I got is
My array with size 3 is saved as String in Mapping2 array.
Please help. Stuck badly. :(
From Mongoose's documentation I believe there's no need to use $set. Just pass an object with the properties to update :
Mappings.findOneAndUpdate(
query,
mapp, // Object containing the keys to update
function(err, object) {...}
);

Correct way to use array of sub documents in Mongoose

I'm just learning Mongoose, and I have modeled Parent and Child documents as follows..
var parent = new Schema({
children: [{ type: Schema.Types.ObjectId, ref: 'Child' }],
});
var Parent = db.model('Parent', parent);
var child = new Schema({
_owner: { type: Schema.Types.ObjectId, ref: 'Parent' },
});
var Child = db.model('Child', child);
When I remove the Parent I want to also remove all the Child objects referenced in the children field. Another question on Stackoverflow states that the parent's pre middleware function can be written like this...
parent.pre('remove', function(next) {
Child.remove({_owner: this._id}).exec();
next();
});
Which should ensure that all child objects are deleted. In this case, however, is there any reason to have the children array in the Parent schema? Couldn't I also perform create/read/update/delete operations without it? For example, if I want all children I could do?
Child.find({_owner: user._id}).exec(function(err, results) { ... };
All examples I've seen of Mongoose 'hasMany' relations feature a 'children' array though but don't show how it is then used. Can someone provide an example or give a purpose for having it?
Well you are defining two different collections, Child and Parent in ->
var Parent = db.model('Parent', parent);
var Child = db.model('Child', child);
So you are free to user the Child model as you like by doing add/remove/update. By saving it as a 'ref' you can do:
Parent.find({_id:'idofparent'})
.populate('children')
.exec((err, result) => { .... } )
Your Parent will then be populated with their children.!

Mongoose find/update subdocument

I have the following schemas for the document Folder:
var permissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
var folderSchema = new Schema({
name: { type: string },
permissions: [ permissionSchema ]
});
So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.
I could easily save the whole Folder document with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }, function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
but the problem is that perm is always undefined! I tried to "statically" fetch the permission in this way:
var perm = data.permissions[0];
and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.
Any idea?
P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!
So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _id value for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $ operator variable, which holds the "matched" position in the array:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$": permission
}
},
function(err,doc) {
}
);
That .findOneAndUpdate() method will return the modified document or otherwise you can just use .update() as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $ as mentioned earlier.
Then of course you are using the $set operator so that only the elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation" and just specify the elements you actually want to update. As in:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$.role": permission.role
}
},
function(err,doc) {
}
);
So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.
What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.
So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.
In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.
const userSchema = new mongoose.Schema({
// ...
addresses: [addressSchema],
});
If you have an array of subdocuments, you can fetch the desired one with the id() method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set() method.
User.findById(userId)
.then((user) => {
const address = user.addresses.id(addressId); // returns a matching subdocument
address.set(req.body); // updates the address while keeping its schema
// address.zipCode = req.body.zipCode; // individual fields can be set directly
return user.save(); // saves document with subdocuments and triggers validation
})
.then((user) => {
res.send({ user });
})
.catch(e => res.status(400).send(e));
Note that you don't really need the userId to find the User document, you can get it by searching for the one that has an address subdocument that matches addressId as follows:
User.findOne({
'addresses._id': addressId,
})
// .then() ... the same as the example above
Remember that in MongoDB the subdocument is saved only when the parent document is saved.
Read more on the topic on the official documentation.
If you don't want separate collection, just embed the permissionSchema into the folderSchema.
var folderSchema = new Schema({
name: { type: string },
permissions: [ {
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
} ]
});
If you need separate collections, this is the best approach:
You could have a Permission model:
var mongoose = require('mongoose');
var PermissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
module.exports = mongoose.model('Permission', PermissionSchema);
And a Folder model with a reference to the permission document.
You can reference another schema like this:
var mongoose = require('mongoose');
var FolderSchema = new Schema({
name: { type: string },
permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});
module.exports = mongoose.model('Folder', FolderSchema);
And then call Folder.findOne().populate('permissions') to ask mongoose to populate the field permissions.
Now, the following:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
The perm field will not be undefined (if the permission._id is actually in the permissions array), since it's been populated by Mongoose.
just try
let doc = await Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{ "permissions.$": permission},
);

Return updated collection with Mongoose

I work with nodejs/express/mongoose/angularjs. I'd like to update a collection named Lists which has several properties, one of which is an array of items. In the following code, I'm pushing a new task items in the items array. Everything works fine, however the update function does not sends back the updated collection, then I must perform another query on the database. Is there a more efficient way to do this ?
The nodejs/express code :
exports.addTaskToList = function(req, res) {
var listId = req.params.Id;
var taskId = req.params.TaskId;
Lists.update({_id: listId}, {$push: {items: taskId}}, {safe:true, upsert: true}, function(err, result){
if(err) {
console.log('Error updating todo list. ' + err);
}
else{
console.log(result + ' todo list entry updated - New task added');
Lists.findById(listId).populate('items').exec(function (err, updatedEntry) {
if (err) {
console.log('Unable to retrieve todo list entry.');
}
res.send(JSON.stringify(updatedEntry));
});
}
});
};
Furthermore, the array items is an array of ObjectIds. Those items are in a separate schema so in a separate collection. Is it possible to push the whole object and not only its _id so that there is not another collection created ?
use findOneAndUpdate() method and in query parameter use option as { "new": true}
return this.sessionModel
.findOneAndUpdate({user_id: data.user_id}, {$set:{session_id: suuid}}, { "new": true})
.exec()
.then(data=>{
return {
sid: data.session_id
}
})
The update method doesn't return the updated document:
However, if we don't need the document returned in our application and
merely want to update a property in the database directly,
Model#update is right for us.
If you need to update and return the document, please consider one of the following options:
Traditional approach:
Lists.findById(listId, function(err, list) {
if (err) {
...
} else {
list.items.push(taskId)
list.save(function(err, list) {
...
});
}
});
Shorter approach:
Lists.findByIdAndUpdate(listId, {$push: {items: taskId}}, function(err, list) {
...
});
Regarding your last question:
Is it possible to push the whole object and not only its _id so that
there is not another collection created ?
The answer is yes. You can store sub-documents within documents quite easily with Mongoose (documentation on sub-documents here). By changing your schema a little, you can just push your whole item object (not just item _id) into an array of items defined in your List schema. But you'll need to modify your schema, for example:
var itemSchema = new Schema({
// Your Item schema goes here
task: 'string' // For example
});
var listSchema = new Schema({
// Your list schema goes here
listName: String, // For example...
items: [itemSchema] // Finally include an array of items
});
By adding an item object to the items property of a list, and then saving that list - your new item will be persisted to the List collection. For example,
var list = new List({
listName: "Things to do"
});
list.items.push({
task: "Mow the lawn"
});
list.save(function(error, result) {
if (error) // Handle error
console.log(result.list) // Will contain your item instance
});
So when you load your list, the items property will come pre-populated with your array of items.
This is because Items will no longer persist it a separate collection. It will be persisted to the List collection as a sub-document of a List.

Categories

Resources