Mongoose dynamic sub document schema - node.js

I have an Inquiry schema:
const inquirySchema = new mongoose.Schema({
client: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Client' }],
data: dynamicSchema?
}, {
timestamps: true
});
I would like to populate the "data" property field with a sub-document, but I want it to accept different sub-document schemas. I have an "Event" and a "Property" child schema which can be inserted as "data". How do I allow this in my Inquiry schema? It seems I have to actually specify WHICH sub-document schema it expects...
My Child schemas:
const eventSchema = new mongoose.Schema({
name: { min: Number, max: Number },
date: { type: Date },
zone: { type: String }
});
const propertySchema = new mongoose.Schema({
price: { min: Number, max: Number },
status: { type: String },
zone: { type: String }
});

you can make your data as type : ObjectId without defining any reference in the schema, and when you want to populate them, use path and model in the populate to populate from different collection, but you must have a logic for choosing which collection to populate from.
Here is how you can do the same:
inquirySchema
const inquirySchema = new mongoose.Schema({
client: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Client' }],
data: { type: mongoose.Schema.Types.ObjectId }
}, {
timestamps: true
});
Populating data
if(isEvent)
{
//Populate using Event collection
Inquiry.find({_id : someID}).
populate({path : 'data' , model : Event}).
exec(function(err,docs){...});
}
else if(isProperty)
{
//Populate using Property collection
Inquiry.find({_id : someID}).
populate({path : 'data' , model : Property}).
exec(function(err,docs){...});
}

Related

Pushing items to array in mongoose

i am trying to create items to specific id of collection using nodejs and mongoose but i am getting CastError which i shared below picture. So goal is, I binded items id in collection schema because collection has many items and when i create items to the specific id of collection i want to push them to items array in the collection schema.
ERROR
ITEMS ROUTE
itemRouter.post("/:collectionId", JWTAuthMiddleware, async (req, res, next) => {
const {name} = req.body
if (req.params.collectionId.length !== 24)
return next(createHttpError(400, "Invalid ID"));
const collection = await CollectionModal.findByIdAndUpdate(
req.params.collectionId,
{
$push : { items: { ...req.body, owner: req.user._id, id: uuidv4() } },
},
{ new: true }
);
if (!collection)
return next(
createHttpError(
400,
`The id ${req.params.collectionId} does not match any collections`
)
);
res.send(collection);
});
Collection schema
import mongoose from "mongoose";
const { Schema, model } = mongoose;
const collectionSchema = new Schema(
{
name: { type: String },
description: { type: String },
topic: { type: String },
image: { type: String },
additionalCustomFields: {
fieldNumber: { type: Number },
fieldName: { type: String },
fieldType: { type: String },
fieldChecked: { type: Boolean },
fieldDate: { type: Date },
},
owner: { type: Schema.Types.ObjectId, ref: "User" },
items: [{ type: Schema.Types.ObjectId, ref: "Item" }],
},
{ timestamps: true }
);
// collectionSchema.index({ "$**": "text" });
export default model("Collection", collectionSchema);
ITEM schema
import mongoose from "mongoose";
const { Schema, model } = mongoose;
const itemSchema = new Schema(
{
name: { type: String },
description: { type: String },
topic: { type: String },
image: { type: String },
comments: [
{
user: { type: Schema.Types.ObjectId, ref: "User", required: true },
text: { type: String },
},
],
tags: { type: String },
owner: { type: Schema.Types.ObjectId, ref: "User" },
likes: [{ type: Schema.Types.ObjectId, ref: "User" }],
collections: { type: Schema.Types.ObjectId, ref: "Collection" },
},
{ timestamps: true }
);
itemSchema.index({ "$**": "text" });
export default model("Item", itemSchema);
Are you sure that using referenced documents is the correct way to go here? Mongoose is complaining because you are trying to push whole objects to the "items" array, instead of ObjectIds of referenced documents.
You have 2 choices here:
1. use embedded documents
This way you can easily store objects directly to the items array, which does not allow you to store the Items in a seperate collection
2. first create Item documents, then push the reference to items array
In this case you have to first create the items in Item collection. Afterwards you can map the result to only ObjectIds and push these to items array of your collection document.

How to populate sub document of another model in mongoose?

I have two mongodb model as following.
const CompanySchema = new Schema(
{
sections: [{
name: { type: String },
budgets: [{ // indicates from CalcSchema
index: { type: Number },
title: { type: String },
values: [Number],
sum: { type: Number, default: 0 },
}],
}]
},
{ timestamps: true }
);
const CalcSchema = new Schema({
budget: {
type: Schema.Types.ObjectId, // I want to populate this field. this indicates budget document in Company model
ref: "Company.sections.budgets" //it's possible in mongoose?
},
expense: {
type: Number,
default: 0
}
});
budget field indicate one of budgets field in CompanySchema.
So I want to populate when get Calc data.
But I don't how to populate embedded document.
I tried set ref value to ref: "Company.sections.budgets". but it's not working.
Please anyone help.
Finally, I found answer myself.
There is useful plugin for it.
https://github.com/QuantumGlitch/mongoose-sub-references-populate#readme
And I learned that my schema structure was wrong. It's anti-pattern in mongodb.

How to populate fields present inside a map type schema in mongoose?

I have my mongoose schema as follow
const instructorSchema = new mongoose.schema({
coursesByMe: [
{
course: {
type: ObjectId,
ref: "Course",
},
submissions: {
type: Map,
of: Submission.schema,
},
},
],
active: {
type: Boolean,
default: false,
},
});
My submission schema is a simple schema
const submissionSchema = new mongoose.Schema({
user: {
type: ObjectId,
ref: "User",
},
subFile: String,
});
module.exports = mongoose.model("Submission", submissionSchema);
I want to populate the user field but I am unable to as the type of submissions is of map type schema.
Any help with this would be really grateful. Thank you.

How can i query the data in a sub-document of a document from a sub-document of another document

This is my category schema. For every category document, there is a subdocument of subcategories
#category schema
const CategorySchema = mongoose.Schema({
name: {
type: String, required: true
},
subcategories: [{
name: {
type: String, required: false
},
}],
created : {
type : Date,
default: Date.now()
}
});
let Categories = mongoose.model('categories', CategorySchema);
# selected category schema
const BusinessCategoriesSchema = mongoose.Schema({
business_id: {
type: String
},
category_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "categories",
required: true
},
subcategories: [{
subcategory_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "subcategories._id",
required: false
}
}]
});
let businessCategories = mongoose.model('business-categories', BusinessCategoriesSchema);
How can I retrieve the subcategories sub-document from Categories schema when I perform a query on business-categories collection?
#This is the code snippet I am using but am not getting my desired result
const query = await businessCategories.find({business_id : businessId })
.populate('category_id')
.populate('subcategories.subcategories_id');
I know I am not doing something right.
I was able to get the category name from categories schema but I could not get the subcategories sub-document from the categories schema.
Thank you in advance

Mongoose populate a field without ObjectId?

I have a schema:
var Schema = mongoose.Schema;
var TicketSchema = new Schema({
externalId: String,
name: String,
items: [{
externalId: String,
price: Number,
quantity: {
type: Number,
default: 1
},
comment: {
type: String,
default: '',
trim: true
},
entity: {
type: String,
ref: 'Entity'
}
}],
tableId: String
});
mongoose.model('Ticket', TicketSchema);
And I want to populate entity field with an unique field other than ObjectId.
How can I achieve that?
Though late answer. Please check Populate Virtuals for Mongoose 4.5.0
Click the link below
http://mongoosejs.com/docs/populate.html
And scroll down or search for Populate Virtuals you will see it does exactly what you want.
I found Views as one useful approach, though not sure it is the most efficient! For example, in movielens database, I wanted to refer 'movieId' in ratings collection to 'movieId' in the movies collection using 'movieId' as foreign key.
db.createView('rating-movie-view','ratings',[{$lookup:{from:"movies",localField:"movieId",foreignField:"movieId",as:"ratings_movie"}},{ $project:{'userId':1,'movieId':1,'rating':1,'timestamp':1,'ratings_movie.title':1,'ratings_movie.genres':1 } }])
New view "rating-movie-view" thus created has the required fields 'title and 'genres'.
db["rating-movie-view"].findOne()
{
"_id" : ObjectId("598747c28198f78eef1de7a3"),
"userId" : 1,
"movieId" : 1129,
"rating" : 2,
"timestamp" : 1260759185,
"ratings_movie" : [
{
"title" : "Escape from New York (1981)",
"genres" : "Action|Adventure|Sci-Fi|Thriller"
}
]
}
Hope this useful!
Those who are not familiar with movielens data here are the schema
var MovieSchema = new mongoose.Schema({
movieId: Number,
title: String,
genres: String,
});
var RatingSchema = new mongoose.Schema({
userid: Number,
movieId:Number,
rating: Number,
timestamp:Number,
});
//View schema
var RatingViewSchema = new mongoose.Schema({
userid: Number,
movieId:Number,
rating: Number,
timestamp:Number,
rating_movie:{title:String,genres:String}
});
const blogs = this.blogModel
.find(find)
.populate('blogCategory', 'name -_id');
Note -_id will exclude the object _id
I'm not sure if I understood your question correctly.
In Mongoose model, in case we do not specify a primary key, it automatically adds in an extra field called ObjectId and assigns a unique value for each object.
In case we need to specify our own key, we can do it by specifying the key property.
For example:
mongoose.model('Todo', {
todoID: {
type: String,
key: true
},
text: {
type: 'text'
},
done: {
type: Boolean,
default: false,
},
date: {
type: Date,
},
items: [{
entity: {
type: String,
ref: 'Entity'
}
}]
});
I hope, this is what you meant.
If you are asking about fetching objects based on Items -> entity's property,
Todo.find({'items.entity.type':required_type}, function(err, foundTodos){
// ---
});
Thanks,
Use crypto to hash something unique like the objectId , and then save it to your entities.
Var hash = crypto.createHmac('sha256', ticket.objectId).digest('hex');
Ticket.entities= hash;

Resources