Express: mongodb mongoose linking entities - node.js

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) {
//...
});
});

Related

mongoose schema, is it better to have one or have several for different tasks?

so I'm been making a site that has comments section, messaging, profile and shopping for the user. I been wondering about when making a schema for those functions, is it better to have all in one schema like
userSchema {
name: String,
....
....
}
or have them seperate like
userSchema {
}
commentSchema {
}
gallerySchema {
}
No one can give you clear answer for this, everyone has different views.
Basically, It depends on your project's scalability
As I see your requirement for this project
You can create a single schema and use it as embedded form, but it's not a very good idea if you are scaling the app.
My recommendation is to create the separate schema for all the tasks which will be easy to debug,scaling the app and in readable form.
Edit
If you are creating separate schema and want to connect them then you can use populate on the basis of ObjectId
See the docs to populate collections
Example
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
Population
Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
});

Updating a Record in Mongo After Retrieving Its ID From Another Record

I am trying to make an API point that would do the following. I submit an Object ID in the path. The record with that ID is found. Then, the program looks into a certain field of this object. The field contains an ObjectID for another entry in the database. At last, I need to pull up that record and increment a certain field in it.
In short, I have a child->parent relationship between certain records and would like the ability of incrementing a certain field within the parent record by submitting the child's id to the API point.
Here is the code I had that did the basic child increment. How can I go about doing it for the parent?
router.get('/today/parent/up/:id', function(req, res){
var collection = db.get('Activity');
collection.update({
_id: req.params.id
},
{
$inc: {
"repetitions.today": 1,
"repetitions.total": 1
}
}, function(err, activity){
if (err) throw err;
res.json(activity);
});
})
First use mongo references, heres documenttion:
https://docs.mongodb.com/manual/reference/database-references/
here's mongoose documentation
http://mongoosejs.com/docs/2.7.x/docs/populate.html
Basically You need to do this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var PersonSchema = new Schema({
name : String
, age : Number
, stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});
var StorySchema = new Schema({
_creator : { type: Schema.ObjectId, ref: 'Person' }
, title : String
, fans : [{ type: Schema.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);
Then you could use .populate() method, and then you could extract your populated model and make changes and save them with .save(), but remember to use it in populated model, not the parent one. For ex. You've got author which contains reference to books, so you make request
author.findOne({'name': 'King'}).populate('books').exec((err, king) => {
let book0 = king.books[0];
book0.title = 'I need to change this one';
book0.save((err, data) => {
console.log('saved referenced object')
}
})

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!

mongoose find a document by reference property

I have a first model Person:
var personSchema = new Schema({
firstname: String,
name: String
});
module.exports = mongoose.model('Person', personSchema);
And a second model Couple:
var coupleSchema = new Schema({
person1: [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
module.exports = mongoose.model('Couple', coupleSchema);
I find a couple with a person ObjectId:
Couple.find({
'person1': req.params.objectid
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But I would like to find a couple by giving a firstname and not an ObjectId of a Person, something like that:
Couple.find({
'person1.firstname': "Bob"
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But it is always empty...
Anyway to solve this?
Thank you for any feedback.
EDIT
I just implemented the answer:
Let's see my Couple model now:
var Person = require('mongoose').model('Person');
var coupleSchema = new Schema({
person1 : [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2 : [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
coupleSchema.statics.findByUsername = function (username, callback) {
var query = this.findOne()
Person.findOne({'firstname': username}, function (error, person) {
query.where(
{person1: person._id}
).exec(callback);
})
return query
}
module.exports = mongoose.model('Couple', coupleSchema);
With this usage:
Couple.findByUsername(req.params.username, function (err, couple) {
if(err)
res.send(err);
res.json(couple);
});
That works! Thank you for your answer and edits.
In your couple model, person1 is an ObjectID (I know you know it), so it has no obviously no property .firstname.
Actually the best way to achieve this, is to find the user by it's first name, and then query the couple, with the id of the user.
This method could/should stand in the couple model as a static method (simplified code sample):
couple.statics.findByPersonFirstname = function (firstname, callback) {
var query = this.findOne()
Person.findOne({firstname: firstname}, function (error, person) {
query.where($or: [
{person1: person._id},
{person1: person._id}
]).exec(callback);
})
return query
}
Just like this exemple.
EDIT: Also note that the ref must be the _id (so you couldn't store with the first name, that would be a bad idea anyway).
Considering your edit:
Person._id is maybe a String and the reference is an ObjectId, if so, try:
{person1: mongoose.Types.ObjectId(Person._id)}
Also, your variable is person and not Person. Try to log person to see if you get something.
Finally, my code sample is really simple, don't forget to handle errors and all (see the link I gave you above, which is complete).

Why is my mongoose document property not properly setting to another _id property

I am trying to set up a simple mongoose test file, and am getting some pretty confusing results. When I run this following code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/myapp');
var personSchema = Schema({
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
creator : { type: Schema.Types.ObjectId, ref: 'Person' },
title : String,
fans : [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
var aaron = new Person({name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) console.log("something didnt work!");
var story1 = new Story({
title: "Once upon a timex.",
creator: aaron._id // assign the _id from the person to creator
});
console.log(aaron._id);
story1.save();
});
Story.findOne({ title: 'Once upon a timex.' },function(err,story){
console.log(story); // printing here
});
I get this output:
{
_id: 54b9e08ed983b41d432473e4,
title: 'Once upon a timex.',
_creator: 0,
__v: 0,
fans: []
}
54bcacb4c812ec812382b6b2
there are many things that don't really make sense in this. As you can see from my code I only console.log(); 2 things:
arron._id
the story document created in the save callback of aaron
Question 1:
When we print out the story object, we see that the creator field is set to 0 (we'll get to this later), and underscore has been added for some reason (I assume it's because it links to an ObjectId). I also tried to add an underscore to creator, like it showed in the documentation, and this caused the creator property to simply not get saved to the document. Could anybody explain how _ interacts with mongoose?
Question 2:
When we try to set creator:aaron_id it gets set to 0, we know that arron._id is not 0, because we print it successfully in the same scope. What am I doing wrong?
Question 1: The issue is that that the find query executes before your save query does (remember that they execute asynchronously).
Try:
var aaron = new Person({name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) console.log("something didnt work!");
var story1 = new Story({
title: "Once upon a timex.",
creator: aaron._id // assign the _id from the person to creator
});
console.log(aaron._id);
story1.save(function(err){
Story.findOne({ title: 'Once upon a timex.' },function(err,story){
console.log(story); // printing here
});
});
});
Question 2: Are you sure you're not looking up older objects that you attempted to save? I have a feeling that at some point story had the property _creator and you changed it to 'creator', but its finding your older document because you are not querying by id. Try changing your story query to:
Story.findOne({ title: 'Once upon a timex.', creator: aaron._id },function(err,story){
console.log(story); // printing here
});

Resources