I had a very weird issue with the way Mongoose interacted with my Node and Mongo database.
I was using express to create a basic get api route to fetch some data from my mongodb.
I had a database called test and it had a collection call "billings"
so the schema and route was pretty basic
apiRouter.route('/billing/')
.get(function(req, res) {
Billing.find(function(err, billings) {
if (err) res.send(err);
// return the bills
res.json(billings);
});
});
Where "Billing" was my mongoose schema. that simply had 1 object {test: string}
This worked fine, I got a response with all the items in my mongo db called "billings" which is only one item {test: "success"}
Next I created a collection called "historys"
I setup the exact same setup as my billings.
apiRouter.route('/historys/')
// get all the history
.get(function(req, res) {
Historys.find(function(err, historys) {
if (err) res.send(err);
// return the history
res.json(historys);
});
});
where again "Historys" was my mongoose schema. This schema was identical in setup to my billings since I didnt have any real data, the fields were the same, i just had it with a test field so the json object returned from both billings and historys should have been
{ test: "success" }
However, this time I didnt get any data back, I just got an empty object
[].
I went through my code multiple times to make sure maybe a capital got lost, or a comma somewhere etc, but the code was identical. the setup and formatting in my mongodb was identical. I went into robomongo and viewed the database and everything was named correctly.
Except, I had 2 new collections now.
My original : "Historys" AND a brand new collection "Histories"
Once i fixed my api route to go look at Histories instead of Historys, I was able to get the test data successfully. I still however cannot pull data from Historys, its like it doesnt exist yet there it was in my robomongo console when I refreshed.
I searched all my code for any mention of histories and got 0 results. Where did the system know to fix the grammar on my collection?
From the docs:
When no collection argument is passed, Mongoose produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option.
So, when you did, in your schema definition, this:
mongoose.model('Historys', YourSchema);
, mongoose created the Histories collection.
When you do:
db.historys.insert({ test: "success" })
through mongodb console, if the historys collection doesn't exist, it'll be created. That's why you have the two collections in your db. Like the docs said, if you don't want mongoose to create a collection with a pluralized name based on your model, just specify the name you want.
Related
First time working with MongoDB. This query works for me in cmd line:
db.secrets.update({_id: ObjectId("5f767cd481cea1687b3dbf86")}, {$set: {secret_rating: 5}})
However, updating a record using essentially the same query on my node server is not completing this task when pinged. Am I wrong in trying to query for a record like so in my model? ObjectId obviously isn't native to my server.
db.secrets.update({_id: "5f767cd481cea1687b3dbf86"}, {$set: {secret_rating: 5}})
Assuming you're using the Nodejs Mongodb driver and not some ORM (since it hasn't been specified), two points of concern:
As far as my knowledge serves, if you have a connection object to your desired database in the db variable, you cannot reference collections directly such as you've done with db.secrets; you must instead use the collection method like so:
const secrets = db.collection("secrets");
secrets.find({
/*your query here*/
}).then((results) => {})
So, unless you've assigned db.secrets with db.collection("secrets") you should be getting an error, Cannot read property "update" of undefined. But I'm going to assume you've got the collection object in db.secrets since you did not mention you're getting that error.
You seem to be using a string instead of an ObjectID object. You can import the ObjectID constructor from the nodejs driver like so:
const ObjectID = require('mongodb').ObjectID
Then in your query, you will have to make a new ObjectID to get the correct result:
db.collection("secrets").find({
_id: new ObjectID("5f767cd481cea1687b3dbf86")
}).then((results) => {})
NOTE: The ObjectID constructor will throw an error if the string supplied to it is not a valid, 24-char hex string, so, if you're getting the id string as an input from somewhere (say, as a parameter in an API or as a command line argument), you might want to wrap it in a function that handles that error.
I am fairly new to MongoDB and Mongoose, I am really confused about why some of the middleware works at the document and some works on query. I am also confused about why some of the query methods return documents and some return queries. If a query is returning document it is acceptable, but why a query return query and what really it is.
Adding more to my question what is a Document function and Model or Query function, because both of them have some common methods like updateOne.
Moreover, I have gathered all these doubts from the mongoose documentation.
Tl;dr: the type of middleware most commonly defines what the this variable in a pre/post hook refers to:
Middleware Hook
'this' refers to the
methods
Document
Document
validate, save, remove, updateOne, deleteOne, init
Query
Query
count, countDocuments, deleteMany, deleteOne, estimatedDocumentCount, find, findOne, findOneAndDelete, findOneAndRemove, findOneAndReplace, findOneAndUpdate, remove, replaceOne, update, updateOne, updateMany
Aggregation
Aggregation object
aggregate
Model
Model
insertMany
Long explanation:
Middlewares are nothing, but built-in methods to interact with the database in different ways. However, as there are different ways to interact with the database, each with different advantages or preferred use-cases, they also behave differently to each other and therefor their middlewares can behave differently, even if they have the same name.
By themselves, middlewares are just shorthands/wrappers for the mongodbs native driver that's being used under the hood of mongoose. Therefor, you can usually use all middlewares, as if you were using regular methods of objects without having to care if it's a Model-, Query-, Aggregation- or Document-Middleware, as long as it does what you want it to.
However, there are a couple of use-cases where it is important to differentiate the context in which these methods are being called.
The most prominent use-case being hooks. Namely the *.pre() and the *.post() hooks. These hooks are methods that you can "inject" into your mongoose setup, so that they are being executed before or after specific events.
For example:
Let's assume I have the following Schema:
const productSchema = new Schema({
name: 'String',
version: {
type: 'Number',
default: 0
}
});
Now, let's say you always want to increase the version field with every save, so that it automatically increases the version field by 1.
The easiest way to do this would be to define a hook that takes care of this for us, so we don't have to care about this when saving an object. If we for example use .save() on the document we just created or fetched from the database, we'd just have to add the following pre-hook to the schema like this:
productSchema.pre('save', function(next) {
this.version = this.version + 1; // or this.version += 1;
next();
});
Now, whenever we call .save() on a document of this schema/model, it will always increment the version before it is actually being saved, even if we only changed the name.
However, what if we don't use the .save() or any other document-only middleware but e.g. a query middleware like findOneAndUpdate() to update an object?
Then, we won't be able to use the pre('save') hook, as .save() won't be called. In this case, we'd have to implement a similar hook for findOneAndUpdate().
Here, however, we finally come to the differences in the middlewares, as the findOneAndUpdate() hook won't allow us to do that, as it is query hook, meaning it does not have access to the actual document, but only to the query itself. So if we e.g. only change the name of the product the following middleware would not work as expected:
productSchema.pre('findOneAndUpdate', function(next) {
// this.version is undefined in the query and would therefor be NaN
this.version = this.version + 1;
next();
});
The reason for this is, that the object is directly updated in the database and not first "downloaded" to nodejs, edited and "uploaded" again. This means, that in this hook this refers to the query and not the document, meaning, we don't know what the current state of version is.
If we were to increment the version in a query like this, we'd need to update the hook as follows, so that it automatically adds the $inc operator:
productSchema.pre('findOneAndUpdate', function(next) {
this.$inc = { version: 1 };
next();
});
Alternatively, we could emulate the previous logic by manually fetching the target document and editing it using an async function. This would be less efficient in this case, as it would always call the db twice for every update, but would keep the logic consistent:
productSchema.pre('findOneAndUpdate', async function() {
const productToUpdate = await this.model.findOne(this.getQuery());
this.version = productToUpdate.version + 1;
next();
});
For a more detailed explanation, please the check the official documentation that also has a designated paragraph for the problem of having colliding naming of methods (e.g. remove() being both a Document and Query middleware method)
I wanted to update the field data only ,but my code it adding an object each time i am calling update api.I have gone through many sites and found out updateOne is the method but couldnt end up undersatnding how to implement here.I am quite new to node so any help would be appreciated.
const update=(req,res)=>{
console.log(req);
models.detailsSchema.findByIdAndUpdate(req.body.vehicleId,req.body.data,{new:true}).then((msg,err)=>{
if(err)
res.status(400).json({err})
res.status(200).json({
"resCode":"0000",
"resStatus":"Success",
"resMsg":msg
})
});
}
Looks like you're using Mongoose connected to a MongoDB instance? If that's the case, Schema.findByIdAndUpdate works on the primary key or ObjectId of the record you're trying to update. To make this code work, if my assumptions are correct, change to this:
models.detailsSchema.findByIdAndUpdate(req.body._id, req.body.data, { new:true })
Of course, you're going to want to put in some check to make sure _id is defined if this is a create/update route.
I have a mongoose schema collection A. Then, I create a new object as follows
var myA = new A({
name: 'A simple name'
});
As long as I know, once we have done this, a new _id attribute is created for that object. This means that at the end of the day, myA should look (and it actually looks) like
{
name: 'A simple name',
_id: ObjectId
}
the problem is that when I hit
myA.save()
it is saved into mongodb with a different _id than the previously created.
An idea on why this could be happening? I'm using mongoose 4.4.8
I guess when you create a new document with new A({... it internally marks itself as new document which is evaluated later when calling save() to generate an appropriate MongoDB statement - and this 'flag' is probably not updated immediately when calling save(). This has the positive side effect of having the possibility to quickly create clones of documents when you want to generate test data :)
If you want to update your newly saved document right after creation then you should do that in the callback of the save method which gets the saved document in its 2nd parameter.
I am trying to use the new unstable version of mongoose >4.0.0 to validate update queries.
say that i want to update a schema using the following query
schema.update({_id:'blah'},{a:'blah'},function(err){
//do your thing
})
so lets say i have the following schema,
var schema = new Schema({
a:{type:String}
});
schema.pre('update',function(next){
var findQuery=this._conditions; // gives {_id:'blah'}
// how do i get {a:'blah'}????
next();
});
how do i get the update query of {set:{a:'blah'}} in the pre middleware so i can do some checks before executing the update?
alternatively i know that the update query can be accessed in the post middleware, in
schema.post('update',function(){
var findQuery=this._conditions; // gives {_id:'blah'}
var updateQuery=this._update; //gives {$set:{a:'blah'}}
next();
});
but thats too late, i need this in the pre middleware to check before actually updating the db.
i tried looking through the 'this' object of the pre middleware but cannot find the updateQuery object anywhere and this._update is undefined in the pre middleware.
Is there a way to do this?
thanks
In case you're still looking for a solution that works on array operations, it looks like in newer versions of mongoose (at least 4.0.7+), this._update is defined in the pre-middleware.
I found a work around through this particular example, however it doesnt quite solve my actual problem. what you can do in mongoose version ~4.0.0 is to let the pre middleware specify to go through the model validation on update.
schema.pre('update',function(next){
this.options.runValidators = true; // make sure any changes adhere to schema
})
basically, you can then specify the validators inside the schema
var schema = new Schema({
a:{
type:String,
validate:[...] //the validation you want to run
}
});
you can choose to skip the validation on a normal save operation by using the this.isNew check inside validation functions.
this code will run validate:[...] on any $set and $unset to a in your update query.
however, it doesn't work on array operations like $push or $addToSet for some reason. so if your updating an array, it won't run the validation code at all! hence it doesn't solve the actual problem im faced with. but it can work with the example provided for anyone that comes across this particular problem