Reference is not checking exact type - mongodb

I defined a model as mentioned below:
var QuestionSchema = new Schema({
askedBy: {type:ObjectId, Ref:'User'},
relatedCourses: [
{type: ObjectId, ref:'Course'}
]
})
module.exports = mongoose.model('Question', QuestionSchema, 'Questions')
(Because of a bug) At the time of saving/creating new document of type Question, relatedCourses field is assigned to an array of object ids other than 'Course', and mongoose didn't produce any error, and saved the document.
It took sometime to dig and find this error.
My question is, why mongoose not checking exact Schema type? What is the need of mentioning 'Course' in ref field when any ObjectId is sufficient?

I found mongoose-id-validator, which is doing the required type validation.

Related

Can database references in mongodb reference two databases?

I am using MongoDB via mongooose. Can database references reference two databases at the same time?
field_name: {
type: mongoose.Schema.Types.ObjectId,
ref: 'collectionA',// can I reference collectionA and collectionB too?
required: true,
},
See code above.
The field, field_name, can be an objectId from collectionA or collectionB. How can I reflect that in my mongoose schema?
I guess you are looking for mongoose dynamic reference via refPath.
const someSchema = new Schema({
field_name: {
type: Schema.Types.ObjectId,
required: true,
refPath: 'onCollection'
},
onCollection: {
type: String,
required: true,
enum: ['collectionA', 'collectionB']
}
});
In this case, Instead of a hardcoded model name in ref, refPath means Mongoose
will look at the onCollection property to find the right model.
For example if we have this document:
{
field_name: some_id,
onCollection: 'collectionA'
}
Collection.find().populate('field_name') will populate the field from collectionA. And if the onCollection field was valued with collectionB, it would have populated it from collectionB.
This scenario only works if you want to reference one collection at a time, but the collection is dynamic.
If you need to reference both collections at the same time, there is no mongoose schema design to support array of references as far as I know.
You can just ignore ref in your schema, and pass in the value of ref when you want to populate:
populate({
path: 'field_name',
model: 'collectionA'
})
Then you can have multiple populates. Same applies for $lookup.

Mongoose mixed SchemaType

I couldn't understand that for what purpose mongoose schemaType is used for. If someone could explain it will be helpful.
I'm have to reference another schema from a schema i want to know if we can get the details of all schema together when we do a findOne() on mongoose.
mixed schema means whatever you want the type to be. if you input a String, Number, Date, mongoose will let you do that. However according to documentation, mongoose ref does not work with mixed.
Note: ObjectId, Number, String, and Buffer are valid for use as refs.
if you use mixed, and ref it, you won't be able to query it back.
If you start all over(delete the database and reinsert again), use ObjectId instead of Mixed.
var storySchema = Schema({
author : { type: ObjectId, ref: 'Person' },
});
If you wish to retain old database, the best way is to change mixed to string
var storySchema = Schema({
author : { type: String, ref: 'Person' },
});

How to dynamically populate mongoose document reference at runtime?

I have a schema that has a field that could reference different schema.
var HistorySchema = new Schema({
type: {type: String, required: true},
objectId: {
type: Schema.Types.ObjectId,
required: true,
},
changed: {type: Schema.Types.Mixed}
})
The documents of this schema allows me to keep track of changes happens in different types of objects with objectId.
For example, if User has changed name from 'John' to 'Steve', a History document would have:
{
type: 'User',
objectId: '55fa6bf0831ba3fa0879e7e8',
changed: {name: {oldValue: 'John', newValue: 'Steve'}}
}
Obviously, type can be many different things.
My question is, can I magically populate the objectId field without knowing type before the query?
I know I can do:
History.query({...}).populate('objectId', null, 'User').exec(...);
But that requires me to know the type is User when the query is constructed.
And obviously I can do a second query manually given the type and objectId.
For example, is it possible to save the ref type of a document (not schema) at runtime and take advantage of that? I look around and don't seem to find a way.

Embedded document without Array?

I understand how to embed documents in Mongoose, and they seem fairly straightforward if storing as arrays, for which the use case is fairly obvious:
var CommentSchema = new Mongoose.Schema({...});
var BlogPostSchema = new Mongoose.Schema({
comments : [CommentSchema],
});
But, what I don't see how to do after looking over the documentation forward and backward, is how to store a single sub-document that doesn't need or want to be in an Array.
var UserSchema = new Mongoose.Schema({...});
var BlogPostSchema = new Mongoose.Schema({
author: ??? // 'UserSchema' and UserSchema do not work here.
});
Is there any way to make this work? I don't want to just store the ObjectId, but rather, store a complete copy of the User record, but don't need or want an array.
You cannot embed schemas in this way, with the reasoning that those child docs would be confused with full documents, see this bug thread, where it is stated:
the reason we haven't added this support in the past is b/c this leaves us wondering if all pre-hooks will be executed the same way for the pseudo-child document as well as it implies that we can call save() on that child.
The answer here is to share not the schema, but just the definition.
var userdef = { name: String };
var UserSchema = new Schema(userdef);
var BlogPostSchema = new Schema({author: userdef});
This would result in a nested user object, without actually nesting the Schema.
Just sharing information doesn't support validation bubbling. And you may need validation of UserSchema also.
Instead I recommend array length validation
author: {type:[UserSchema], validate: function (arr) { return arr.length == 1 }},
UPDATE:
In case anyone comes across this now, as of Mongoose 4.2.0 single embedded subdocuments exist! :)
http://mongoosejs.com/docs/subdocs.html#single-embedded

Searching embedded documents Mongoose + nodejs

Im new to Mongoose, and i'm facing a problem in searching.
These are my Schemas:
var CommentSchema = new Schema({
body : String
, comments : [CommentSchema]
});
var PostSchema = new Schema({
body : String
, comments : [CommentSchema]
});
There is a deep nesting of comments. When somebody answers to the existing comment, how can I find that one?
you can look at the mongoose test suite on github for examples.
model_querying_test
Here is what you are looking for:
test finding based on embedded document fields:
function () {
var db = start(), BlogPostB = db.model('BlogPostB', collection);
BlogPostB.create({comments: [{title: 'i should be queryable'}]}, function (err, created) {
should.strictEqual(err, null);
BlogPostB.findOne({'comments.title': 'i should be queryable'}, function (err, found) {
should.strictEqual(err, null);
found._id.should.eql(created._id);
db.close();
});
});
},
One solution to this is to store Comments as a separate Model which you can query directly, and store references to the related ObjectIds and paths between Comments and Posts.
Using the Populate feature in Mongoose related documents can function similarly to embedded documents, although there are some important differences in the way you query them, and you have to be more careful to keep the relationships populated.
Set it up like this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.Types.ObjectId;
var PostsSchema = new Schema({
body : String,
stories : [{ type: ObjectId, ref: 'Story' }]
});
var CommentsSchema = new Schema({
body : String,
post : { type: ObjectId, ref: 'Post' },
comments : [{ type: ObjectId, ref: 'Comment' }]
});
var Story = mongoose.model('Post', PostsSchema);
var Comment = mongoose.model('Comment', CommentsSchema);
If you do it this way it requires more queries to get the post with all its comments (which will be slower than being able to load the Post and its complete comment hierarchy with a single query) however you'll be able to query comments directly and retrieve the Post they were made on (but not easily find the full path to the comment when its nested).
These are all trade-offs; the best decision (either to recursively search for comments, or store them independently then recursively load them) should be made in the context of your application and its expected usage patterns.
One other caveat; the populate feature is currently limited to a single-level of linked ObjectIds; you have to call it on each comment that is returned to get the full nested dataset. There are several plugins that help with this, such as mongoose-subpopulate, and soon enough it'll be supported natively in Mongoose - see the github issue here.