TL;DR
How do you reference (and thus populate) subdocuments within the same collection?
I've been trying for a while now to populate a reference to a subdocument in my Mongoose schema. I have a main schema (MainSchema) which holds arrays of locations and contacts. The locations have a reference to these contacts.
In my location array i make a reference to these contacts by the _id of the contact. See below.
import mongoose from 'mongoose';
const LocationSchema = new mongoose.Schema({
city: {type: String},
contact: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Contact' //alternative tried: refPath: 'contacts'
}
});
const Location = mongoose.model('Location', LocationSchema);
const ContactSchema = new mongoose.Schema({
firstName: {type: String},
lastName: {type: String}
});
const Contact = mongoose.model('Contact', ContactSchema );
const MainSchema = new mongoose.Schema({
name: {type: String},
locations: [LocationSchema],
contacts: [ContactSchema]
});
export.default mongoose.model('Main', 'MainSchema');
Now when i want to populate the contact of the locations I get null or just the plain _id string returned. Below is my populate code. I've tried every combination i could find, involving making the nested documents their own models and trying different ways to reference them.
MainSchema.statics = {
get(slug) {
return this.findOne({name})
.populate('locations.contact')
.exec()
.then((company) => {
if (company) {
return company;
}
const err = 'generic error message';
return Promise.reject(err);
});
}
};
I've also tried the newer approach to no avail:
populate({
path: 'locations',
populate: {
path: 'contacts',
model: 'Contact'
}
});
I must be missing something here. But what?
edited the question to show the complete query statement as requested
After searching some more i found an exact same case posted as an issue on the Mongoose github issue tracker.
According to the prime maintainer of Mongoose this form of populate is not possible:
If you're embedding subdocs, you're not going to be able to run
populate() on the array, because the subdocs are stored in the doc
itself rather than in a separate collection.
So taking your example of schema I would do the following. Please take notice that I don't say that my approach is the best way of doing this, but I had an exact similar case as you.
Mongoose model
import mongoose from 'mongoose';
const LocationSchema = new mongoose.Schema({
city: {type: String},
contact: { type: Schema.Types.ObjectId, ref: 'Contact'}
});
const ContactSchema = new mongoose.Schema({
firstName: {type: String},
lastName: {type: String}
});
const MainSchema = new mongoose.Schema({
name: {type: String},
locations: [{ type: Schema.Types.ObjectId, ref: 'Location' }],
});
const Main = mongoose.model('Main', MainSchema);
const Location = mongoose.model('Location', LocationSchema);
const Contact = mongoose.model('Contact', ContactSchema );
Note: In your main schema I've removed the contacts since I understand from your example that each location has it's own contact person, so actually in the MainSchema you don't need the ContactSchema
The controller where the you insert the data
The idea here is that you have to pass the reference _id from each document to the other one, the below example is mock-up please adapt it to fit your app. I used a data object that I assume that has one location and one person contact
async function addData(data) {
//First you create your Main document
let main = await new Main({
name: data.name
}).save();
//Create the contact document in order to have it's _id to pass into the location document
let contact = await new Contact({
firstName: data.fistName,
lastName: data.lastName
});
//Create the location document with the reference _id from the contact document
let location = await new Location({
city: data.city,
contact: contact._id
}).save();
//Push the location document in you Main document location array
main.locations.push(location);
main.save();
//return what ever you need
return main;
}
Query
let mainObj = await Main.findOne({name})
.populate({path: 'locations', populate: {path: 'contact'}})
.exec();
This approach is working for me, hope it serves you as well
Related
I'm using Mongoose and a MongoDB.
I have three schemas user and genres and songs.
const userSchema = new Schema({
name: String,
age: String,
favorite_genres: {
type: Schema.Types.ObjectId,
ref: 'Genre'
}
});
const genreSchema = new Schema({
name: String,
songs: {
type: Schema.Types.ObjectId,
ref: 'Song'
}
});
const songSchema = new Schema({
title: String,
artist: String,
length: String,
year: String
});
those are simplifid schemas, but they will do for this puprose.
So when I query a user I want to populate the doc with the favorite genres. But there (due to the potential amount of data) not with the ids of all songs, but only the number of songs. How can I achieve that?
I tried virtuals but they don't work the way I wanted it.
What I'm looking for is sth like the following:
const user = await User.findById(userID).populate({path: 'favorite_genres', select: 'name', sizeof: 'songs'}).lean()
// or
const user = await User.findById(userID).populate({path: 'favorite_genres', select: ['name', 'songs.length']}).lean()
Does anyone have an idea?
I am trying to populate by using Mongoose. This is what it looks like so far:
schema.ts
const UserSchema = new mongoose.Schema({
id: String,
name: String,
});
const UserModel = mongoose.model("User", UserSchema, "users");
const ShiftSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
},
date: {
type: String,
required: true,
},
startTime: String,
endTime: String,
});
ShiftSchema.virtual("user", {
ref: "User",
localField: "userId",
foreignField: "id",
justOne: true,
});
const ShiftModel = mongoose.model("Shift", ShiftSchema, "shifts");
export default ShiftModel;
shift.ts
const shifts = await ShiftModel.find({}).populate("user");
So this code is working fine, it is working as expects and populating user data into shift data.
What I'm currently having trouble with is organizing this. I need to separate schemas into separate files however if I try to separate the userSchema and UserModel into a different file, populate doesn't work anymore. Any ideas on a workaround for this?
Also, by the way, I am using a custom ID and not the default ID supplied by MongoDB.
What I've tried:
import mongoose from "mongoose";
export const UserSchema = new mongoose.Schema({
id: String,
name: String,
});
export const UserModel = mongoose.model("User", UserSchema, "users");
When I try to run my script, I get an error saying that Schema hasn't been registered for model "User".
I´m using mongoose and I need to find a model name from a model instance.
In one part of the code I have:
const schema = new mongoose.Schema({
name: {
type: String,
required: true
},
phone: {
type: String,
required: true
}
}
const schema = new mongoose.Schema('MyData', schema);
let instance = new this({
name: 'Pete',
phone: '123'
});
Ths instance variable is passed around in my code. Later I need to find out instance name, but I´m no sure if there is a way to do it, something like:
let instanceName = getInstanceName(instance); <== Expects 'MyData' to be returned
Is that possible using mongoose ?
I realized I had a model not an instance of a model so I needed to use something else.
If you have a model, you can get the name as below:
const model = mongoose.model("TestModel", schema);
const collectionName = model.collection.collectionName;
If you have a specific item/instance of the model:
const instance = new model({...});
const collectionName = instance.constructor.modelName
as Hannah posted.
The name of the model can be accessed using this instance.constructor.modelName.
In my case I was looking for how to get a discriminator model name from a mongoose model, and the suggested solutions didn't work:
const PerformanceResult = DropDown.discriminator('PerformanceResult', new db.Schema({
name: { type: String, required: true }
}))
export default PerformanceResult
console.log(PerformanceResult.constructor.modelName) // undefined
console.log(PerformanceResult.collection.collectionName) // DropDown (parent name)
you can use this:
console.log(PerformanceResult.modelName) // PerformanceResult
mongoose version: "^5.11.8"
I want to do something like this:
var userSchema = new Schema({
local: localSchema,
facebook: facebookSchema,
twitter: twitterSchema,
google: googleSchema
});
But it seems that a Schema is not a valid SchemaType.
In the SubDocuments guide, they only give an example where the child schema is put inside of an array, but that isn't what I want to do.
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
children: [childSchema]
})
It looks like you're just trying to create a sub object for each of those properties. You can accomplish this one of two ways.
Embedded in the schema itself
var userSchema = new Schema({
local: {
someProperty: {type: String}
//More sub-properties...
}
//More root level properties
});
Reusable object to be used in multiple schemas
//this could be defined in a separate module and exported for reuse
var localObject = {
someProperty: {type: String}
//more properties
}
var userSchema = new Schema({
local: localObject
});
var someOtherSchema = new Schema({
test: localObject
});
I have the following mongoose schema:
var ChatSchema = new Schema({
pin: String,
users: [{type: mongoose.Schema.Types.ObjectId, ref: "User"}],
messages: [{type: mongoose.Schema.Types.ObjectId, ref: 'Message'}], //<----
active: Boolean,
});
var MessageSchema = new Schema({
sent: Date,
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
content: String
});
var UserSchema = new Schema({
name: String,
pin: String,
id: String
});
This function is defined for the ChatSchema:
ChatSchema.methods.addMessageForUser = function(message, userid, userpin ) {
chat = this;
module.exports.User.findOne({id: userid, pin: userpin}).populate('messages').exec(function(err, user) {
message = {
user: user,
time: new Date(),
message: message,
};
chat.messages.push(message);
chat.save();
});
};
When I run it, I get the following error:
CastError: Cast to ObjectId failed for value "[object Object]" at path "messages"
If I remove populate('messages);` Then the error goes away, but I get another error because I try to use the messages array.
Here is the code for the models:
module.exports.Message = mongoose.model('Message', MessageSchema);
module.exports.User = mongoose.model('User', UserSchema);
module.exports.Chat = mongoose.model('Chat', ChatSchema);
Based on what you've got here, you're trying to populate backwards.
If each User had an array of Messages, then this populate call would work. It's a method on the mongoose Query object, in this case, and so it's looking for a property called messages on the documents in the User collection you're querying to pull ids from - since these aren't there, you get a weird error.
Based on what you've got here, it looks like it will work if you just remove the populate call.