How to properly use populate in mongoose - node.js

I'm trying to populate a reference field in NodeJS using mongoose for mongodb interaction.
I have the following code:
Housekeeping model
...
user: {
type: mongoose.Schema.Types.ObjectId
required: true
ref: "User"
}
...
User model
...
userSchema.virtual("housekeepings", {
ref: "Housekeeping",
localField: "_id"
foreignField: "user"
}
...
Housekeeping router
...
const housekeepings = await Housekeeping.find({day:day, house: house})
.populate("user")
.exec(function (err, housekeeping) {
if(err) return
console.log("Username is: " + housekeeping.user.name
)
})
...
This code results in cannot read property "name" of undefined (I do have documents in the database that should be loaded)

Because you're using Model.find() so your housekeeping will be an array. That's why housekeeping.user is undefined. If you want to find one document then use Model.findOne() instead. Also you don't need to use .exec() with await. Just:
const housekeepings = await Housekeeping.find({day:day, house: house}).populate("user");
console.log(housekeepings[0].user.name)
Or:
const housekeeping = await Housekeeping.findOne({day:day, house: house}).populate("user");
console.log(housekeeping.user.name)

So apparently the problem was that I changed the field name in "housekeeping model" from "owner" to "user" without updating the database and POST method.

Related

How i can to create new model inside another model?

I have TweetModel and i need to create RatingModel inside, and RatingModel must have a same TweetModel id, because i want to link them for further deletion.
Maybe there are other ways to make rating for posts in mongoose.
I will be very grateful for your help
const tweet = await TweetModel.create({
text: req.body.text,
images: req.body.images,
user: user._id,
rating: ..., // => {retweets[], likes[]}
})
tweet.save()
TweetSchema
RatingSchema
Assuming you already know how to create Schema, you can create 2 different schemas for your tweet and rating. This will create 2 separate collections will inturn be more manageable.
const { text, images } = req.body
const objectId = new mongoose.Types.ObjectId() //create a objectId first.
await TweetModel.updateOne({ _id }, { text, images, user: user._id }, { upsert: true })
await RatingModel.updateOne(....)
We are using updateOne here with the option of {upsert: true} where we will create a new record if record _id don't exist.
You will be able to get the ratings easily with mongoose.populate()
More on { upsert: true } - https://docs.mongodb.com/manual/reference/method/db.collection.update/
Bonus
If you want to create the record in half the time.
const [ tweet, rating ] = await Promise.all([
TweetModel.updateOne({ _id }, { text, images, user: user._id }, { upsert: true })
RatingModel.updateOne(....)
])
Using Promise.all you can execute both update request at the same time.
Bonus 2
If you want to populate ratings inside tweet, add this in your Schema..
TweetModel.virtual('rating', {
ref: 'Rating',
localField: '_id',
foreignField: '_id',
});
Scroll to Populate - https://mongoosejs.com/docs/tutorials/virtuals.html

How do I prevent Schema fields from being inserted into subdocument?

I'm making a dating app in node js and vue, and everything works however I wish to exclude password from being inserted into subdocument upon creation of a conversation. Right now I know that i can say .select('-password') when using User.findOne() but it doesn't work, when adding the user schema as a subdoc to my Conversations schema, which has user_one and user_two, each referring to a User schema. I need the password field, so I can't ommit it when creating a schema. Right Now my code looks like this:
User.findOne({ _id: fromUserId }, (errUserOne, userOne) => {
User.findOne({ _id: toUserId }, (errUserTwo, userTwo) => {
conversation = new Conversation({
_id: mongoose.Types.ObjectId(),
user_one: userOne,
user_two: userTwo
});
conversation.save();
const message = new Message({
_id: mongoose.Types.ObjectId(),
conversation_id: conversation._id,
message: req.body.message,
user_id: fromUserId
});
message.save();
res.sendStatus(201);
});
});
However this code saves the password to the Conversation collection, which I don't want.
User.find({ _id: :{ $in : [fromUserId,toUserId] }, { password:0 } , (err, userArray) => {
//your code goes here
});
Two things, You are querying two time for getting users. You can merge it into single query and for excluding the password field you can pass {password:0}. Which will exclude it in the documents.
also while you define Conversation schema don't make user_one and user_two type of user. Instead define only whatever properties of user you want to save like:
var Conversation = new Schema({
_id : ObjectId,
user_one : {
_id: ObjectId,
//all other fields
},
user_two : {
_id: ObjectId,
//all other fields
},
});

Mongodb with Node Js

How can i execute this mongodb query through node js (mongoose).
i have two tables with the follwoing schemas,
i want to fetch username and password from users table and fetch full name from the info table.
var infoSchema = mongoose.Schema({
khatam_id: String,
user_id: String,
fullname: String,
});
var usersSchema = mongoose.Schema({
user_id: String,
username: String,
password: String,
});
i don't know if you're a hot shot but if you are you can use this.
userSchema.virtual('infos',
{
ref: 'Info',
localField: 'user_id',
foreignField: 'user_id',
})
Assuming u name you're infoSchema as Info model ,mongodb converts it to infos in case you didn't know thats why the virtual field will be called as infos ref obviously a reference to Info model
localField is the field referring the userSchema unique id and foreignField is the field u are referring in the infoSchema which matches the unique value that localfield u mentioned .
Finally, along with all the fields in your userSchema add this
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
so when u query for user
Give it a shot it really comes in handy.
Note: it doesn't actually create a field in the database (virtual duh.) it just populates your response object for front end rendering which is actually better.
Connect to MongoDB: Make sure MongoDB service is running before the program execution.
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/your-database");
mongoose.Promise = global.Promise;
var connection = mongoose.connection;
connection.once('open', function() {
console.log('connected to database);
});
connection.on('error', function() {
console.error('Mongoose connection error");
});
process.on('SIGINT', function() {
mongoose.connection.close(function() {
console.log('Mongoose connection disconnected due to app SIGINT.');
});
});
create user schema :
const Schema = mongoose.Schema;
const usersSchema = new Schema({
user_id: String,
username: String,
fullname: String
});
const Users = mongoose.model('Users', usersSchema );
Run Query like this:
Users.findOne({query})
.then(function(user){
// do something
})
.catch(function(err){
// handle error
})
Assuming you know how to set setup mongoose and connect your database and only need the correct query let me share something i noticed with the models you provided. I don't think there is a need for 2 collections as you can store the same thing under one collection because it saves the time for lookup for the user data from info schema. So user schema can be
var usersSchema = mongoose.Schema({
user_id: String,
username: String,
password: String,
khatam_id: String,
fullname: String
});
And using the following query you can get the user details
Users.findOne({})
.then(function(user){
// do something
})
.catch(function(err){
// handle error
})
This is much more efficient and faster compared to using aggregate queries or mongoose populate functions.
If the above method is not suitable for you you can try the mongoose populate function.
UserSchema = new mongoose.Schema({
user_id: String,
password: String,
},
// schema options: Don't forget this option
// if you declare foreign keys for this schema afterwards.
{
toObject: {virtuals:true},
// use if your results might be retrieved as JSON
// see http://stackoverflow.com/q/13133911/488666
//toJSON: {virtuals:true}
});
UserInfoSchema = new mongoose.Schema({
user_id: String,
khatam_id: String,
username: String,
fullname: String,
});
// Foreign keys definitions
UserSchema.virtual('userDetails', {
ref: 'UserInfoSchema',
localField: 'user_id',
foreignField: 'user_id',
justOne: true // for many-to-1 relationships
});
// Models creation
var UserSchema = mongoose.model('UserSchema', UserSchema);
var UserInfoSchema = mongoose.model('UserInfoSchema', UserInfoSchema);
// Querying
UserSchema.find({...})
// if you use select() be sure to include the foreign key field !
.select({.... user_id ....})
// use the 'virtual population' name
.populate('userDetails')
.exec(function(err, books) {...})
install mongoose in your machine
npm install mongoose
import mongoose lib
const mongoose = require('mongoose');
const Schema = mongoose.Schema, ObjectId = Schema.ObjectId;
connect to mongodb and create schema for your table
mongoose.connect('mongodb://localhost/myappdatabase');
const usersSchema = new Schema({
_id: ObjectId,
user_id: String,
username: String,
fullname: String
});
const Users = mongoose.model('Users', usersSchema );
Find user from "Users" table using mongo query
Users.find({<here you can write your mongo query>}, function (err, docs) {
// docs.forEach
});
you have to use aggregate
db.users.aggregate([
{
$lookup:
{
from: "info",
localField: "user_id",
foreignField: "_id",
as: "userInfo"
}
},
{ "$unwind": "$userInfo" },
])
You can use this query to get those types of records what you want:
db.users.aggregate([
{ $lookup: {
from: "info",
localField: "user_id",
foreignField: "user_id",
as: "userInfo"}},
{ "$unwind": "$userInfo" }])

Mongoose populate return undefined

I'm currently trying to develop an app using mongo and node.js.
I am facing a problem when I want to build a query who use the populate option.
Here are my Schemas :
// Schema used by mongoose
var userSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
login: String,
password: String,
movies: [ { type: mongoose.Schema.Types.ObjectId, ref: movieModel} ],
admin: Boolean
},{ collection: "user" });
var movieSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
title: String,
}, { collection: "movie" });
As you can see, each user have an array of movies, this array contains valid ids of movies. What I want is to have the movies of an user. This is how I build my query :
var query = userModel.findOne({ login: req.session.user["login"] })
.populate("movies");
query.exec(function(err, user)
{
if (err)
throw err;
console.log(user.movies[0].title);
});
The query is executed successfully, but when I try to display the title of the first movie at the console.log line I got an error "TypeError: Cannot read property 'title' of undefined". I checked the documentation of mongoose and don't understand why I'm getting this error.
I would like to specify that my database contains valid data.
I put mongoose in debug mode, and this is the query that is executed :
Mongoose: user.findOne({ login: 'user' }) { fields: undefined }
Mongoose: user.find({ _id: { '$in': [ ObjectId("52e2a28949ad409834473e71"), ObjectId("52e2a28949ad409834473e79") ] } }) { fields: undefined }
The two ids on the second line are valid ids of movies. I would like to display their name.
Thanks a lot for your help.
What is the value of this: ref: movieModel?
movieModel would need to be set to the string like "Movie". See here for more information. It will need to match the identifier provided when you create the Movie model.
var Movie = mongoose.model('Movie', movieSchema);
So, you might have in a schema:
var userSchema = mongoose.Schema({
name: String,
favorite_movies: { type: Schema.Types.ObjectId, ref: 'Movie' }
});
var User = mongoose.model('User', userSchema);
I've used the string Movie in both the Schema definition and when creating the Movie type. They need to be exactly the same.
MongooseJs uses the string name of the Model to determine where to fetch the documents from when using ref and populate.
In the debug output, you can see how Mongoose is actually querying the wrong collection, as I'd expect it to be using movies.find to find the relevant Movie documents.

Trying to remove a subdocument in Mongoose gives me an internal mongoose error

I have a schema as follows (simplified):
var Permission = new Schema({
_id: String, // email address
role: String // "admin" or "member"
});
var Org = new Schema({
name: {type: String, index: {unique: true, dropDups: true}, trim: true},
permissions: [Permission]
});
An example document would look like this:
{
"name": "My Org",
"permissions" : [
{"_id" : "joe#gmail.com", "role" : "admin"},
{"_id" : "mary#gmail.com", "role" : "member"}
]
}
I am trying to delete one of the permissions rows, using the command org.permissions.remove(req.params.email), as shown in context below:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org
.findOne({name: name})
.select()
.exec(function(err, org) {
if (err) return Org.handleError(res, err);
if (!org) return Org.handleError(res, new Error("#notfound " + name));
org.permissions.remove(req.params.email);
org.save(function(err, org) {
if (err) return Org.handleError(res, err);
else return res.send(org);
});
});
};
When I do this, I get the following error:
TypeError: Cannot use 'in' operator to search for '_id' in joe#gmail.com
at EmbeddedDocument.Document._buildDoc (/../node_modules/mongoose/lib/document.js:162:27)
at EmbeddedDocument.Document (/../node_modules/mongoose/lib/document.js:67:20)
at EmbeddedDocument (/../node_modules/mongoose/lib/types/embedded.js:27:12)
at new EmbeddedDocument (/../node_modules/mongoose/lib/schema/documentarray.js:26:17)
at MongooseDocumentArray._cast (/../node_modules/mongoose/lib/types/documentarray.js:62:10)
at Object.map (native)
at MongooseDocumentArray.MongooseArray.remove (/../node_modules/mongoose/lib/types/array.js:360:21)
at model.Org.methods.removePermissions (/../models/org.js:159:20)
The only thing I can think of is that Mongoose does not support _id fields that are not ObjectID's? This is strange, because I use these elsewhere in my code and it works fine (e.g. org.permissions.id("joe#gmail.com") works).
Any suggestions much appreciated!
I'm not sure why using remove there isn't working, but you can do this atomically with findOneAndUpdate and the $pull operator:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org.findOneAndUpdate(
{name: name},
{$pull: {permissions: {_id: req.params.email}}},
function(err, org) {
// org contains the updated doc
...
});
};
As per this answer, you need to call remove() on the subdocument you want to remove, rather than on the entire subdocument array.
So, change:
org.permissions.remove(req.params.email);
to:
org.permissions.id(req.params.email).remove();
This two-step method has the added advantage over the answer supplied by #JohnnyHK in that you can validate whether the subdocument actually exists before removing it. This can be useful if you'd like to send a 404 response indicating that the subdocument doesn't exist - as far as I am aware, this isn't possible using the $pull atomic operator.
Note that this also will only work if your subdocument array has a schema, as illustrated in the question. If it doesn't, or it has a schema type of Mixed, the collection returned from the database will be a plain array rather than a Mongoose-enhanced array. This means that there is no .id() function. In this case, I would use lodash#remove instead:
_.remove(org.permissions, (function(permission) {
return permission._id.toString() === req.params.email;
}));

Resources