Mongoose - Retrieving object from ref query - node.js

I've got the following schemas:
var userSchema = new Schema({
firstName: String,
lastName: String,
emailAddress: {type: String, set: toLower, index: {unique: true}},
});
var eventMemberSchema = new Schema ({
user: { type : Schema.ObjectId, ref : 'User' },
created: { type: Date, default: Date.now }
});
var eventSchema = new Schema({
id : String,
name : String,
startDate : Date,
endDate : Date,
venue : { type : Schema.ObjectId, ref : 'Venue' },
invitees : [eventMemberSchema],
});
What I'm trying to do, is query the events, with an invitation._id, and ultimately get back the user...
invitees->eventMember->user
So far i've got:
Event
.find({ "invitees._id": req.query.invitation_id })
.populate('user')
.run(function (err, myEvent) {
console.log('error: ' + err);
console.log('event: '+ myEvent);
})
This works, and console shows the output of myEvent...
(I realise I don't need the populate part of my mongoose query above for this... i'm just testing)
I'm struggling on how to get, what I'd basically describe as: myEvent.invitees.user
EDIT
As an update...
This works - however, it kind of sucks, as now i'll need to do another db operation to get the user (i realise ref in mongoose does this under the hood)
Event
.findOne({ "invitees._id": "4f8eea01e2030fd11700006b"}, ['invitees.user'], function(err, evnt){
console.log('err: '+ err);
console.log('user id: '+ evnt.invitees[0].user); //this shows the correct user id
});

Try
Event
.find({ "invitees._id": req.query.invitation_id })
.populate('invitees.user')
Update:
Here is a working gist.

Related

Mongoose query in an Object

Question, I got a schema using mongoose which created an Object. I don't know how to query the author id which will return the list of the object under the author Id
var mongoose = require("mongoose");
var bookedSchema = new mongoose.Schema({
origin: String,
destination: String,
author: { id: { type: mongoose.Schema.Types.ObjectId, ref: "User"}, username: String},
Date: {type: Date, default: Date.now}
});
module.exports = mongoose.model("Booked", bookedSchema);
On my route I have query find({}) i want to query the author id instead of {} and it will return the list of object under the author ID. i tried the findById(author:{id:req.user._id}) but it returned null answer. any ideas how to do that. Thank You!
router.get('/myAccount', function(req, res){
Booked.find({}).exec(function(err, booked){
if(err){
console.log(err);
// res.redirect('back');
}else{
console.log(booked)
res.render('accounts/myAccount', {booking:booked});
}
});
});
you should use .populate() for refered documents to populate them.
change this:
Booked.find({}).exec(function(err, booked)
to:
Booked.find({}).populate('author').exec(function(err, booked)
or if you need to find a document by the refered author id you can use this:
Booked.find({}).populate({
path: 'author',
match: { id: { $eq: req.user._id }}
}).exec(function(err, booked)

Express: mongodb mongoose linking entities

I'm building a simple web app where a company sends out a question to its employees requesting for feedback. Still learning about mongodb. Been playing around with it all week & I'm slowly getting a good hang of it with some helpful assistance on the forums but only now I realize I have been using a flawed thought process to design the schema. I was initially using a user's response as a field in the UserSchema but I have now removed it (as commented out here) as I realized this is not a user's property but rather a variable that keeps changing (yes/no/null). I now have to create a separate AnswersSchema (I was told I'll need one but I stubbornly argued against it - saw no sense in at the time I started the project) which I have done now (correct me if it's wrongly written/thought out). My question now is how do I modify my query in the api to link all the three entities together on a save operation in the router post? Please note the save operation code shown here works but is flawed as it's for when the user has a response as one of their properties. So now only the user's name shows up on the angular front-end after I removed response on UserSchema which makes sense.
var QuestionSchema = Schema({
id : ObjectId,
title : String,
employees : [{ type: ObjectId, ref: 'User'}]
});
var UserSchema = Schema({
username : String,
//response : String,
questions : [{ type: ObjectId, ref: 'Question'}]
});
//new schema/collection I've had to create
var AnswerSchema = Schema({
response : {type :String, default:null},
question : { type: ObjectId, ref: 'Question'},
employees : [{ type: ObjectId, ref: 'User'}],
})
module.exports = mongoose.model('Question', QuestionSchema);
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('Answer', AnswersSchema);
api.js
Question.findOne({ title: 'Should we buy a coffee machine?'}).exec(function(err, question) {
//example data
var user = new User([{
"username": "lindelof",
"response": "yes",
},{
"username": "bailly",
"response": "no",
},{
"username": "suzan",
"response": "yes",
}]);
question.employees = [user1._id];
user.questions = [question._id];
question.save(function(err) {
if (err) throw err;
console.log(question);
user1.save(function(err) {
if (err) throw err;
});
});
});
console.log('entry saved >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
}
UPDATE
You did the right thing by adding AnswerSchema, as it's a many to many relationship. A question can be answered by many users (employees). A user can answer many questions. Therefore, it's good to have answer as an associative collection between the two.
With this relationship in mind, you need to change your schema a little:
var QuestionSchema = Schema({
id : ObjectId,
title : String,
//employees : [{ type: ObjectId, ref: 'User'}]
});
var UserSchema = Schema({
username : String,
//response : String,
//questions : [{ type: ObjectId, ref: 'Question'}]
});
var AnswerSchema = Schema({
response : {type :String, default:null},
question : { type: ObjectId, ref: 'Question'},
employee : { type: ObjectId, ref: 'User'}, //a single employee
});
Now, to know if a certain user has answered a question already, just search Answer with his and the question's ids:
Answer.findOne({
question: questionId,
employee: userId
})
.exec(function(err, answer) {
if (err) {
} else if (!answer) {
//the employee has not answered this question yet
} else {
//answered
}
});
Lastly, your submit-answer API should expect a body that contains questionId and userId (if signed in, you can get userId from session or token also). This route updates existing answer, else creates it (for create-only use create function)
router.post('/', function(req, res) {
//req.body = {question: "594315b47ab6ecc30d5184f7", employee: "594315d82ee110d10d407f93", response: "yes"}
Answer.findOneAndUpdate({
question: req.body.question,
employee: req.body.user
},
req.body,
{
upsert: true //updates if present, else inserts
}
})
.exec(function(err, answer) {
//...
});
});

How can I merge results into a single JSON while querying data from two different models in node.js and mongoose?

I have two mongoose models in my app:
var UsersSchema = new Schema({
username: {type: String},
facebook_username: {type: String},
display_name: {type: String}
}
and
var CommentsSchema = new Schema({
user_id: {type: String, required: true},
text_content: {type: String},
photo_content_url: {type: String}
comment_date: {type: Date, default: Date.now},
created_at: {type: Date, default: Date.now},
}
currently each single comment contains user_id - a direct link to its author.
I created a node.js endpoint that takes POST query and returns JSON with all details about comments:
commentsRoutes.post('/comments', function (req, res) {
var startDate = req.body.startDate;
var endDate = req.body.endDate;
var query= {};
query.$and = [];
// and condition on start date
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
var endDate = new Date(req.param('endDate'));
query.$and.push({"comment_date":{$gte: startDate}});
query.$and.push({"comment_date":{$lte: endDate}});
}
var finalquery = Comments.find(query)
finalquery.exec(function(err, comments){
if(err) {
res.send(err);
return;
}
res.json(comments);
});
});
This endpoint returns me a JSON with all the comments, however - I need to attach to each JSON details about its author - username, facebook_username and display_name (fetched based on user_id from UsersSchema). Can you tell me how could I do it?
user_id in CommentsSchema is a mongoose id of a specific user from UsersSchema
====== EDIT
Like #Antonio suggested in his answer below, I used populate in my case, it worked well and I saw merged JSON at the end. All user details were added, it looked like:
{
"text_content": "blah",
"photo_content_url": "http://www...",
"comment_date": "some date",
"created_at": "some date",
"user_id": { "username": "someUsername",
"facebook_username": "fbUsername",
"display_name": "sth" }
}
however - is there a way to include POST parameters in my endpoint that will apply to the user_id json?
Currently I'm sending a POST parameters startDate and endDate. But if I send also facebook_username - how can I include it in a query and find only comments that were written by this author?
I tried adding a simple condition:
var fb_username = req.body.facebookUsername;
query.$and.push({"facebook_username": fb_username});
but it didn't work because there's no such field facebook_username in CommentsSchema...
So how can I include condition attached to fields from UsersSchema that will limit results from CommentsSchema?
Since you have a reference to the corresponding user you could use populate.
Taking into account that try this:
Comments
.find({
comment_date: {
$gte: startDate,
$lte: endDate
}
})
.populate('user_id')
.exec(function(err, comments) {
if(err) {
return res.send(err);
}
res.json(comments);
});
By the way, not related to the main question, but since you are not doing any change in the server I think a GET would be a better option.
I also abbreviated your query, $and is not necessary here.
EDIT
You can add filtering to populate, in your case:
.populate({
path: 'user_id',
match: {
facebook_username: fb_username
}
})

Retrieve Array in Subdocument MongoDB

I have a Users model structure somewhat like this:
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
todosDo: [models.Do.schema],
}
And the child "Do" schema somewhat like this (in a different file):
const doSchema = new mongoose.Schema({
name: {type: String, default : ''},
user: {type: mongoose.Schema.ObjectId, ref: 'User'},
createdAt: {type : Date, default : Date.now}
});
And I'm trying to figure out how to retrieve the todosDo array for the signed in user. This is what I've got so far:
// Get all "Do" todos from DB
// Experimenting to find todos from certain user
User.findById(req.user.id, function(err, user){
if(err){
console.log(err);
} else {
doTodos = user.todosDo, // this obviously doesn't work, just an idea of what I was going for
console.log(doTodos);
finished();
}
});
Am I referencing the child/parent wrong or am I just not retrieving the array right? Any help is greatly appreciated!
As far I guess you may want to edit as raw js objects so you need to use lean() function. without using lean() function user is mongoose object so you can't modify it.
can try this one:
User.findById(req.user.id)
.lean()
.exec(function (err, user) {
if(err){
console.log(err);
return res.status(400).send({msg:'Error occurred'});
}
if(!user) {
return res.status(400).send({msg:'User Not found'});
}
doTodos = user.todosDo;
console.log(user.todosDo); // check original todos
console.log(doTodos);
return res.status(200).send({doTodos : doTodos }); // return doTodos
});
and to refer child schema in parent schema from different model you can access a Model's schema via its schema property.
say in doSchema.js file
const doSchema = new mongoose.Schema({
name: {type: String, default : ''},
user: {type: mongoose.Schema.ObjectId, ref: 'User'},
createdAt: {type : Date, default : Date.now}
});
module.exports = mongoose.model( 'DoSchema', doSchema );
in user.js file
var DoModel = require('./doSchema');// exact path
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
todosDo: [DoModel.schema],
}
Thanks for your help everybody! My problem was that I needed to push all the newly created todos in the post route to todosDo, so then I could retrieve them at the get route. Everything's working now!

How to get around E11000 MongoError without deleting 'unique: true'

I am trying to build a forum in order to learn the MEAN stack. I ran into an issue while using mongoose...
I have this...
var UserSchema = new Schema({
id: ObjectId,
firstName: String,
lastName: String,
role: String,
email: {
type: String,
unique: true
},
password: String,
workers: [WorkerSchema]
});
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: UserSchema,
posts: [PostSchema]
});
var Topic = mongoose.model('Topic', TopicSchema);
app.post('/topics', requireLogin, function(req, res) {
User.findOne({"email": req.session.user.email}, function(err, user) {
if (user.role == "moderator" || user.role == "admin") {
var topic = new Topic({
title: req.body.title,
moderator: req.session.user,
posts: []
});
topic.save(function(err) {
if (err) console.log(err);
res.status(204).end();
});
}
});
});
My issue is this... When I POST a topic to /topics, it works the first time, populating the topics collection with one item. But then, when I POST to /topics again, from the same user, I get an E11000 MongoError that looks like this:
message: 'E11000 duplicate key error index: MY_MONGO_DB.topics.$moderator.email_1 dup key: { : "myuser#example.com" }'
I know that removing the 'unique: true' property from the email field of UserSchema would fix this issue, but I don't want to remove that uniqueness property since I use it elsewhere in my code to ensure that users are unique by email.
Is there any way around this? In other words, is there any way to keep the 'unique: true' property and also retain the ability of users to be able to post multiple topics without triggering the E11000 error?
What you did was to embed the user. In your database, the resulting document would look something like
{
...
moderator: {..., email: "john#example.com"}
}
Which, of course, would violate the unique constraint if you have the same person as a moderator twice.
What you should do instead is to reference the user in your schema:
var user = mongoose.model('User', UserSchema);
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
posts: [PostSchema]
});

Resources