MongoDB and Mongoose - One to Many Relationships - node.js

I'm having a devil of a time understanding the one to many relationship options in MongoDB. I have a fairly simple use-case that I want to prove out, and I can't seem to find any good step-by-steps with my Google (and Stack Overflow)-Fu.
If I access mongo's command line tool, insert a document into the "users" collection with a String array of role_id that corresponds to the _id in the "roles" collection ... how do I tie all of it together on the mongoose side? When I use the findOne method, it just seems to pull the "role_id"s as is, so I know I must be missing something.
If I put all of these roles into the users without any reference what so ever, what happen if I needed to enable or disable roles? Would I need to update every single relevant role in every single user?
Thanks muchly for the assistance and patience for another new Mongoer!

According to your explanation you just forgot to use 'populate' in your query. Please note that results will not contain linked document, so you need to use 'populate' in order to let mongoose know - 'Hey, I want to pull info about this role as well'.
Example:
//promises
User
.findOne(<searchQuery>)
.populate('roles')
.exec()
.then(function(foundUser){
return foundUser; //do something with results (foundUser)
})
.onReject(function(err){
throw err; //do something with error
});
//callbacks
User
.findOne(<searchQuery>)
.populate('roles')
.exec(function(err, foundUser){
if(err){
throw err; //do something with error
} else {
//do something with results (foundUser)
}
});
Please check the following links:
http://mongoosejs.com/docs/2.7.x/docs/populate.html
http://mongoosejs.com/docs/populate.html

Related

Updating Particular field in Nodejs

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.

Most efficient way for checking if mongo document already exist? Query for existing or set index to unique?

I'm looking through a somewhat popular authorization utility, and noticed this:
User.findOne({
email: req.body.email.toLowerCase()
}, function (err, existingUser) {
if (existingUser) {
return res.status(409).send({
message: 'Email is already taken'
});
}
Would it be more efficient to just set the user email to a unique index and handle the error for that, rather than querying to check for one that already exist?
Better to check, than rely on mongodb errors.
Because if something changes inside driver and you will not be aware you'll get a mess.
For example. Mongo version 4.0 will not throw errors on unique index and just replace data with corresponding fields.
Or some developer change createNewUser method and instead of save it now uses update if we have error.
Or something similar.
With validation of email field you will prevent all of those issues and a lot of pain in future.
Hope this helps.

Preventing concurrent access to documents in Mongoose

My server application (using node.js, mongodb, mongoose) has a collection of documents for which it is important that two client applications cannot modify them at the same time without seeing each other's modification.
To prevent this I added a simple document versioning system: a pre-hook on the schema which checks if the version of the document is valid (i.e., not higher than the one the client last read). At first sight it works fine:
// Validate version number
UserSchema.pre("save", function(next) {
var user = this
user.constructor.findById(user._id, function(err, userCurrent) { // userCurrent is the user that is currently in the db
if (err) return next(err)
if (userCurrent == null) return next()
if(userCurrent.docVersion > user.docVersion) {
return next(new Error("document was modified by someone else"))
} else {
user.docVersion = user.docVersion + 1
return next()
}
})
})
The problem is the following:
When one User document is saved at the same time by two client applications, is it possible that these interleave between the pre-hook and the actual save operations? What I mean is the following, imagine time going from left to right and v being the version number (which is persisted by save):
App1: findById(pre)[v:1] save[v->2]
App2: findById(pre)[v:1] save[v->2]
Resulting in App1 saving something that has been modified meanwhile (by App2), and it has no way to notice that it was modified. App2's update is completely lost.
My question might boil down to: Do the Mongoose pre-hook and the save method happen in one atomic step?
If not, could you give me a suggestion on how to fix this problem so that no update ever gets lost?
Thank you!
MongoDB has findAndModify which, for a single matching document, is an atomic operation.
Mongoose has various methods that use this method, and I think that they will suit your use case:
Model.findOneAndUpdate()
Model.findByIdAndUpdate()
Model.findOneAndRemove()
Model.findByIdAndRemove()
Another solution (one that Mongoose itself uses as well for its own document versioning) is to use the Update Document if Current pattern.

Mongo Database Fixing Names after Mongoose Index?

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.

Populate doesn't work Mongoose (no database insertion)

I've got a big problem, i'm trying to use model associations in Mongoose because it would be really useful but there's something i don't get ...
Let's say there's a User model with a schema like ... (among others)
user_geos : [{
type: Mongo.Schema.ObjectId,
ref: 'UserGeo',
}]
And there's a UserGeo model with this schema ... (among others)
_user: {
type: Mongo.Schema.ObjectId,
ref: 'User'
}
Very simple, when I'm creating a new UserGeo, it should automatically add it to the user_geos array within User, right ?
user_geo_infos._user = user.id;
db.UserGeo.create(user_geo_infos, function(error, user_geo) {
catches.error(error);
console.log(user_geo);
});
The big problem I got is when i'm creating it, it fills correctly the "_user" field in UserGeo in the database but the User model doesn't fill itself. The array stay empty ("[]"). A detail but you really understood is UserGeo got one User and User got many UserGeo.
I'm really stuck, did i do something wrong ? I checked everywhere and read the Mongoose documentation like 10 times now ...
Any idea ? Thanks people ;)
Very simple, when I'm creating a new UserGeo, it should automatically add it to the user_geos array within User, right ?
Nope. There is a lot less magic/automatic stuff here than you are hoping for. Mongodb, as well as mongoose, will only ever act upon a single collection at a time. This will do the querying automatically when you load records and use the mongoose .populate helper, but it doesn't help you writing across collections.
Ok I tried to make it manually and it works but i'm quite disappointed ... Looks like this ...
db.UserGeo.create(user_geo_infos, function(error, user_geo) {
db.User.findOne(user.id, function(error, user) {
catches.error(error);
user.user_geos.push(user_geo.id);
user.save();
});
catches.error(error);
console.log(user_geo);
});
After I create a UserGeo I get the User and then push the new data to the User field ...
This is solution is heavy, maybe make some helpers would make it lighter .. Thanks anyway :)

Resources