mongoose: Insert a subdocument NOT an array - node.js

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b

As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

Related

Access a Specific Element of a Field in a Document that is An Array

I am using MongoDB, Mongoose, Node.js and express. I am trying to access a specific element of a field within a document where the field happens to be an array. I have looked at the MongoDB documentation on projection and dot notation. I have also looked at various sites like Geeks for Geeks and Tutorials Point but after experimenting cannot seem to get the right syntax. Here is my model:
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
const QuestionSchema = new Schema ({
userid: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: false
},
title: String,
keyword1: String,
keyword2: String,
keyword3: String,
body: String,
upvotes: {
type: Number,
default: 0
},
datePosted: {
type: Date,
default: new Date()
},
answers: [{
myUUID: mongoose.Schema.Types.ObjectId,
respondentID: Number,
respondent: String,
text: String,
date: Date,
upvotes: Number
}]
});
const Question = mongoose.model('Question', QuestionSchema);
module.exports = Question
Ultimately, I am specifically trying to access all the various "text" elements of the "answers" array. I thought I would get the question first. Then get the array. Then iterate through the array and access and display each "text" element for each "array". Here are my mongoose queries:
const Question = require('../models/Question')
module.exports = async (req,res) =>{
console.log("You are inside displayAnswers!")
const thisQuestionID = req.body.questionID;
console.log(thisQuestionID);
const thisQuestion = await Question.find({"_id" : thisQuestionID}).populate('userid');
console.log(thisQuestion)
const answers = thisQuestion.answers;
console.log(answers)
res.render('displayAnswers', {
answers
})
}
I can tell from the console log output that I am in fact getting the correct 'question' document. I 'thought' that once I had that, that I could just use dot notation to get all the answers as a List (see const answers above) and then I could access each element of that List using the index. But I cannot even seem to get the 'answers' array here. Any thoughts or point me in the right direction?

Nested objects are not update

Allora, I'm using mongoose for the first time and I decided to create 2 schemes: the first one represents a user and the second one represents his enquires. Users have an array of enquires like:
var userSchema = new mongoose.Schema({
name: String,
enquires: { type : [Enquire.schema] , "default" : [] },
});
var enquireSchema = new mongoose.Schema({
status: {type: String, 'default': 'pending'},
enquire: String,
});
I see that if I search for an enquire and update its status, it doesn't update the same enquire on the user's array, meaning that they are different object. I don't want to save an array of IDs as it will be the same as a relational database, so I see only 1 solution which is forgetting about the enquire scheme and use only the User scheme. Is it the way mongoose works? For every relationship do I have to insert everything like nested object?
I think you should use references to achieve what you want to achieve.
For more information on mongoose references and populate see Mongoose Populate documentation.
Try this, It may help you.
User Schema :
var userSchema = new mongoose.Schema({
name: String,
enquires: [{ type : mongoose.Schema.Types.ObjectId , ref : 'Enquiry' }]//array of enquiries
});
var User = mongoose.model('User',userSchema );
module.exports = User;
Enquiry Schema :
var enquireSchema = new mongoose.Schema({
status: {type: String, 'default': 'pending'},
enquire: String,
});
var Enquiry = mongoose.model('Enquiry',enquireSchema );
module.exports = Enquiry ;
Working :
create a new Enquiry.
Push it's ID(_id) into user's enquires array.
var enquiry = new Enquiry();
enquiry.enquire = "Dummy enquiry";//set the enquiry
enquiry.save(function(err,result){
if(!err){
//push 'result._id' into users enquires array
}
});
whenever you update an enquiry, it will be automatically updated in
user's document.
use populate to retrieve user's enquiries.
You can embed sub documents (entity) which has id and is like a document or embed native array like a normal property.
And I think the correct definition for yours is :
var enquireSchema = new mongoose.Schema({
status: {type: String, 'default': 'pending'},
enquire: String,
});
var userSchema = new mongoose.Schema({
name: String,
enquires: { type : [enquireSchema] , "default" : [] },
});
If you use refs in embedded link then there are two separate collections and be like relational db's.

How to reference a Mongoose Schema that hasn't been registered yet

This question is similar to: this question
Working with Mongoose, I have something like the following code. (I've prefixed the files with numbers, so I can load them in the order they appear in the 'models' directory, but still control the loading order.)
In 100-employee.server.model.js:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [EmployeeSchema],
});
mongoose.model('Employee', EmplyeeSchema);
Then, in 200-company-server.js, I have:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
type: Schema.ObjectId,
ref: 'Employee'
}
});
mongoose.model('Company', CompanySchema);
Obviously, this doesn't work, since Company is referenced before it is registered. And, loading these in the opposite order doesn't work for the same reason. What I need, is a logical data structure like:
{
name: 'Acme, Inc',
CEO: {
name: 'Karen',
subordinates: [{
name: 'Bob',
subordinates: [{
name: 'Lisa',
subordinates: []
},
name: 'Jerry',
subordinates: []
}]
}]
}
}
(I think I got all of my brackets in place. I just typed that JSON to illustrate the need.)
I could just use an ObjectId for 'company' in EmployeeSchema, but it doesn't fix the problem. I still get a complaint that Company hasn't been registered.
Someone will ask for the use case, so here it is:
I have a bunch of companies.I have a hierarchy of employees of a company. And, I've got a bunch of companies. For each company, I need to know the CEO, without having to search all of my employees for the one with no parent, that has an ObjectId ref Company, but that still runs into the same problem.
Any suggestions?
OK, I've come up with an answer, or at least a workaround. Doesn't feel very elegant. In fact I actively dislike it, but it works. Would love to see a better solution. In 100-company-server.js, define the parent entity first, like so:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
name: { // This is the company name, so probably a bad example
type: String,
default: ''
}, {
shoeSize: Number
}, etc ...
}
});
mongoose.model('Company', CompanySchema);
The key thing to notice here is that instead of making CEO an ObjectId ref 'Employee' I supplied an object that uses the same properties as my Employee schema. Depending on how you want to use it, you may have to coerce that into an Employee object in the controller. Or, there might be a clever way to use a virtual to do the same thing (http://mongoosejs.com/docs/guide.html) But, the virtual would just have to be defined AFTER the EmployeeSchema and model.
In 200-employee.server.js, something like the following:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
CompanySchema = mongoose.model('Company').schema; // so you can use this
// after your Employee
// is defined
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [], // Just an empty array. You fill it with Employees in the controller. You could use a virtual to do the same thing, probably.
});
mongoose.model('Employee', EmployeeSchema);
So, it's kind of ugly, but it works.
Key points:
In defining CompanySchema, you don't use an ObjectId ref 'Employee'.
Instead, you define an object that looks like a Company object.
Since this object doesn't have an _id, you have to work around that in the
controller. In my case, this works fine. But, for many/most use cases, this
probably would not. You'd have to work around this with clever virtuals
and/or the controller.
Use a simple array, without type, to store your children (suborinates in
this example.
The array works just fine, and returns objects just as if you used an array
of ObjectId ref Employee.
Making the Company refer to an Employee (which hasn't been defined yet), you
have to convert a generic object into an Employee object. But, there are
many ways to do that.
There is, undoubtedly, a better way to do this.

Why Mongoose Populate Required?

Using mongoose populate:
http://mongoosejs.com/docs/populate.html
It seams that mongoose is forcing me to declare a ref value for populate when I first create the document but in my case i don't have the ref info yet. When I try to create a new document while providing an empty string I get to my developer field I get:
{"message":"Cast to ObjectId failed for value \"\" at path \"developer\"","name":"CastError","type":"ObjectId","value":"","path":"developer"}
Object that I'm saving through mongoose:
var Project = {
name: 'Coolproject',
status: 'pending',
developer: '',
type: 'basic',
};
Project.create(req.body, function(err, project) {
if(err) { return handleError(res, err); }
return
});
My Model:
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User'},
type:String
});
Basically I need to set it later, but it doesn't seam like this is possible. Currently my work around is populate it with a dummy user until later but this is less than desirable.
Thoughts?
Update
Realized that if i provide a object id like value (55132a418b3cde5546b01b37) it lets me save the document. Very odd. Guess it just figured it can find the document moves on. Wondering why this doesn't happen for a blank value.
The problem is explained in the error message. You cannot save an Empty String in the place of an ObjectId. The field is not listed as 'required', so there is no problem leaving it out and saving the document.
Code correction:
// you can save this
var Project = {
name: 'Coolproject',
status: 'pending',
type: 'basic',
};
You need to use the sparse index in model.
So, the valid model can have developer equal to nil
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User', sparse:true},
type:String
});
See
http://mongoosejs.com/docs/api.html#schematype_SchemaType-sparse and
http://docs.mongodb.org/manual/core/index-sparse/
for additional info

Use mongoose populate to "join" matching subrecords

Using node.js, mongoose (3.5+), mongodb. Have got two collections in the DB:
var AuthorSchema = new mongoose.Schema({
name: { type: String },
});
var StorySchema = new mongoose.Schema({
title: { type: String },
author: { type: type: Schema.Types.ObjectId },
});
What I would like to do is retrieve an author and populate it with a subcollection (say, "stories") that is looked up from Story and match the author. Yes, much like a SQL join.
All of the examples out there work on the AuthorSchema having an array of objectids that reference StorySchema objects - that works fine. But I want to go the opposite direction; partly due to minimizing insert/updates. If I follow the example, adding a new store requires adding a new Story document and updating the Author. I want to just insert a new Story that references the Author.
I suspect that populate() is the right way to go, but can't get it to work. I'm doing something like this:
Author.find({name: 'Asimov').populate({
path: 'stories',
model: 'Story',
match: {'author': this['_id']},
}).exec(function(err, authors) {
console.log(authors);
})
But this doesn't return any stories member in the returned authors. Is this not a populate() solution? Do I really need to structure the schemas differently? Or is there some other feature of mongoose/mongo that would do what I'm looking for.
In the story schema, do this:
author: { type: type: Schema.Types.ObjectId, ref:'Author' }, //or whatever the model name is
then you can run
Story.find({}).populate('author').exec(function(err,stories) {...});

Resources