Removing Mongo document and all of its references? - javascript

At a high level, I'm looking to remove a document from Mongo as well as its referenced documents. Take a look at the following example Schemas:
var studentSchema = mongoose.Schema({
email: String,
name: String,
_class: { type: String, ref: 'Class' },
books: [{ type: Schema.Types.ObjectId, ref: 'Book' }]
});
module.exports = mongoose.model('Student', studentSchema);
var classSchema = mongoose.Schema({
gradeLevel: Number,
students: [{ type: Schema.Types.ObjectId, ref: 'Student' }]
});
module.exports = mongoose.model('Class', classSchema);
var bookSchema = mongoose.Schema({
author: String,
subject: String,
pages: Number
});
module.exports = mongoose.model('Book', bookSchema);
Now, my question is two fold:
1. If I want to .remove() a Class document from my database AND all Student documents referenced to it, what is the best way of doing this? //currently looping through students with the given class id and individually removing, then finally removing the class document.
2. If I want to .remove() a Class document from the db AND all Students AND their Books, is there a way to do this simply through a special remove statement in mongo? //Currently finding all students, removing their books, then remove themselves, then remove their referenced class.
Ideally, I would like a statement the can remove a mongo document, and anything referenced to it, along with any sub-references that sub-document may have. (e.g: Remove a Class and have mongo auto-remove all Students and their Books) Is this possible?

there is no statement that does what you want in mongoDb or mongoose. MongoDB is not the best choice if such operations that span multiple collections are very important for your application ("no automatically supported joins and referential integrity in MongoDB"). You also might need to model your data more "mongo-like" to achieve what you want.
You can do it more efficiently than looping if you have backreferences. The model.find() function returns a query object, that has a .remove(cb) method
mongooose.model('Student').find({_class: myClassToRemove._id}).remove(function(err) {});
For your books this will not work, because Book does not have a reference to Student. But if you do not share books between different students, then you should just store the books within the student object as "embedded documents" instead of using a different model and references. Then they will be deleted automatically when you delete a student.
If you do share one book instance between multiple students, you can not automatically delete the book when you delete a student, because you don't know whether another student uses the same book instance.

Related

Find all realted matched docs in an array of string

I have a mongoose schema model that have a field name tags , and it is an array of strings to store some tags in it for each document. I want something for example if I have an array of tags like ["test", "testimonials", "test Doc"] tags in it, when i search for test, it returns all documents with tags that they are testimonials or test doc , it should be work for example like wildcards (test*) .... can anyone help ?
this is the model
tags: {
type: [
{
type: String,
},
],
},
First of all, I'd tweak the Schema if possible. Your schema could be changed to this:
tags: [String]
This also just means an array of strings. You don't need to always need to use/specify the type key unless you're planning to add more fields to the tag schema, but it doesn't look like it from the question.
You can do the following to select all documents with a specific tag. Since I don't know what the name of your model is, I'll just call it "Model".
await Model.find({ tags: "tagName" })
OR
await Model.find({ tags: { $elemMatch: { someKey: someValue } } })
The later is only if you have other mongodb documents inside the array. Since you only have strings in the array, use the first method.

Mongoose schema creates empty array field

In my Food schema, I have different fields based on the other field called yummy. For example:
if yummy equals pancake, my fields are dough and jam
if yummy equals pizza, my fields are dough, meat and cheese
etc. It works as expected upon saving docs - I just pass the fields I want to be saved per the document, as none of them are required except yummy.
So if I save:
{
yummy: 'pizza'
dough: 'thin'
meat : 'peperroni',
cheese: [ObjectId("59a65a4f2170d90b0436ed4b")] // refs to the cheeses collection.
}
My document looks like expected.
The problem is with field of type: Array. When I save:
{
yummy: 'pancake',
dough: 'thick',
jam: 'blueberry'
}
My document is saved with an additional cheese: [] field. In my schema, cheese is defined as follows:
Mongoose.model('Food', {
...
cheese: {
type: [{ type: Mongoose.Schema.ObjectId, ref: 'Cheese' }],
required: false
},
...
});
I checked if mongo needs the doc to have an array field predefined as empty in case of $push used in update - it does not. So, the question is: how do I avoid adding an empty array field to my document upon saving?
When you are saving the document, mongoose look at the schema of the related collection.
It takes all fields you are giving to it and set the values up. It also create default values for the fields you did provide nothing for.
That's the behavior of mongoose.
In your case what can we do?
We gonna create two schema related to the same collection. One with the field cheese and one without. So when you gonna save a new document with the schema without cheese, it's not gonna get created.
In practice, the collection will hold two different document type.
Here is a link about using multiple schema related to a single collection
Just set default: undefined to field cheese. According to this.
...
cheese: {
type: [{ type: Mongoose.Schema.ObjectId, ref: 'Cheese' }],
required: false,
default: undefined
},
...

Assign key and variable without need to assign in Schema

I have the following mongodb / mongoose Schema
var forumReplySchema = new Schema({
userid : String,
username: String,
forumid: {type: String, default: '1'},
message: {type: String, default: ''},
time: Number,
});
module.exports = mongoose.model('forum_replies', forumReplySchema);
with following query:
forum_replies.findOne({forumid: forumid}).then(function (reply) {
currentReply.username = user.username;
currentReply.rank = result.rank;
});
Currently username is assigned cause i have username property in the Schema.
Rank is not assigned cause its not in the Schema.
But is there a way for me to assign rank, without having it defined in the Schema?
Edit: aka, assign rank to the forum replies object without the need to save in Db.
you cannot add properties to mongoose document. If you want to do so, you will need to convert it into plain object first. There are couple of ways you could go about it. Following is one of them.
reply.toObject();
Then you can add properties to it.
//you have currentReply in your code, I just want to show general idea here
reply.rank = result.rank;
Is this what you are looking for?
Update:
Thanks for accepting answer :). Also look into lean() option, this returns you plain JS objects without you having to do the conversion manually. lean() also has performance benefit.

MongoDB - Query conundrum - Document refs or subdocument

I've run into a bit of an issue with some data that I'm storing in my MongoDB (Note: I'm using mongoose as an ODM). I have two schemas:
mongoose.model('Buyer',{
credit: Number,
})
and
mongoose.model('Item',{
bid: Number,
location: { type: [Number], index: '2d' }
})
Buyer/Item will have a parent/child association, with a one-to-many relationship. I know that I can set up Items to be embedded subdocs to the Buyer document or I can create two separate documents with object id references to each other.
The problem I am facing is that I need to query Items where it's bid is lower than Buyer's credit but also where location is near a certain geo coordinate.
To satisfy the first criteria, it seems I should embed Items as a subdoc so that I can compare the two numbers. But, in order to compare locations with a geoNear query, it seems it would be better to separate the documents, otherwise, I can't perform geoNear on each subdocument.
Is there any way that I can perform both tasks on this data? If so, how should I structure my data? If not, is there a way that I can perform one query and then a second query on the result from the first query?
Thanks for your help!
There is another option (besides embedding and normalizing) for storing hierarchies in mongodb, that is storing them as tree structures. In this case you would store Buyers and Items in separate documents but in the same collection. Each Item document would need a field pointing to its Buyer (parent) document, and each Buyer document's parent field would be set to null. The docs I linked to explain several implementations you could choose from.
If your items are stored in two separate collections than the best option will be write your own function and call it using mongoose.connection.db.eval('some code...');. In such case you can execute your advanced logic on the server side.
You can write something like this:
var allNearItems = db.Items.find(
{ location: {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <longitude> , <latitude> ]
},
$maxDistance: 100
}
}
});
var res = [];
allNearItems.forEach(function(item){
var buyer = db.Buyers.find({ id: item.buyerId })[0];
if (!buyer) continue;
if (item.bid < buyer.credit) {
res.push(item.id);
}
});
return res;
After evaluation (place it in mongoose.connection.db.eval("...") call) you will get the array of item id`s.
Use it with cautions. If your allNearItems array will be too large or you will query it very often you can face the performance problems. MongoDB team actually has deprecated direct js code execution but it is still available on current stable release.

mongoose js: should documents be embedded when field is not a list?

Below is my Schema.
var UserModel = new Schema({
id: ObjectId
, email: String
, firstName: String
, lastName: String
, password: String
});
var MessageModel = new Schema({
id: ObjectId
, createdDate: { type: Date, default: Date.now }
, user: String // should this be [UserModel]?
, body: String
});
For my case, every message has a user but only one. Should I Embed UserModel or should I leave the user field as a string. One future use case would be to return a query that has the body of the message, creation date, and user (first name and last name concatenated). Thanks.
Short answer: No, you should not use the UserModel as a subdocument of MessgeModel.
Long answer: First, reconsider your naming. You are actually defining schemas here. Later, you will be associating a model with each of these schemas. So, UserSchema and MessageSchema would be more appropriate here.
But that's not germane. Regarding your question, you're MessageModel schema should not contain embedded documents representing users unless there is a 1-to-1 relationship. However, I expect that each user will be associated with many messages (hopefully). So, you don't want a new copy of the user (each with a new _id) for every message he creates. You only want one canonical document for each user, and a reference to that user in the MessageModel.
Now, using a string reference may be the right choice for you. But if you anticipate running a query on the MessageModel in which you'd like the user attribute to be populated by the actual UserModel document, then you'll want to use a ref.

Categories