trying to populate a single field in each object inside an array of objects by dynamically picking the model to use with mongoose - mongodb

I'm trying to use refPath to reference which collection to pull the population data from inside my schema, and even though it looks identical to the examples I've seen, its just not working.
Here is my schema for statesPersons, not super important, but it contains the activeWork array of objects.
import mongoose, {model, Schema} from "mongoose";
const statesPersonsSchema = new Schema(
{
profileId: {
type: String,
required: true,
unique: true,
},
department: {
type: String,
required: true,
index: true,
},
firstName: String,
lastName: String,
location: String,
org: String,
title: String,
jobDescription: String,
email: {
type: String,
lowercase: true,
},
phoneNumber: String,
activeWork: ["activeWork"],
emailList: [String],
jobAssignments: [String],
affiantInfo: {
affiantInfoTitle: String,
affiantInfoExperience: String,
},
assessments: [
{
assessdBy: {
type: Schema.Types.ObjectId,
ref: "statesPerson",
},
dueDate: Date,
questions: {},
},
],
},
{ strictPopulate: false }
);
export default mongoose.model("statesPersons", statesPersonsSchema);
Here is my schema for activeWork, the array of objects. This has the referenceId that I need to populate as well as the collectionType which I pull what collection it is from.
import mongoose, {model, Schema} from "mongoose";
const activeWorkSchema = new Schema(
{
active: Boolean,
collectionType: {
type: String,
enum: ["messages", "cases"],
},
referenceId: {
type: Schema.Types.ObjectId,
refPath: "collectionType",
},
sentBy: {
type: Schema.Types.String,
ref: "statesPersons",
},
sentTo: {
type: Schema.Types.String,
ref: "statesPersons",
},
timeRecived: Date,
dueDate: Date,
subject: String,
viewed: Boolean,
content: {},
},
{ strictPopulate: false }
);
export default mongoose.model("activeWork", activeWorkSchema);
And here is my query.
export async function getStatesPersonsActiveWorkByProfileId(req, res){
mongoose.set('debug', true);
try{
const { profileId } = req.params
const data = await statesPersons
.find({ profileId })
.populate('statesPersons.activeWork.referenceId')
.exec()
return res.send({
message: "success",
data: data,
status: 200 })
}catch(e) {
console.error(e.message)
return res.send({
message: "couldn't fetch active work",
data: null,
status: 500 })
}
}
its returning with the statesPersons object and the activeWork contains the objectId I need to populate, but its not populating. it looks like this.
"activeWork": [
{
"active": true,
"collectionType": "messages",
"referenceId": "63a49e3052658ce60c1dafcb",
"sentBy": "108416469928574003772",
"dueDate": "2018-02-21T11:16:50.362Z",
"subject": "testing",
"viewed": false,
"_id": "63a49e3052658ce60c1dafce"
I can force it to work by changing the query to be explicit.
const data = await statesPersons
.find({ profileId })
.populate({path: 'activeWork.referenceId', model: 'messages'})
.exec()
which looks like this.
activeWork": [
{
"active": true,
"collectionType": "messages",
"referenceId": {
"_id": "63a49e3052658ce60c1dafcb",
"involvedParties": [
"108416469928574003772",
"100335565301468600000"
],
"comments": [
{
"sender": [
"108416469928574003772"
],
"dateSent": "2022-12-22T18:13:04.604Z",
"content": "There is no way this is going to work.",
"_id": "63a49e3052658ce60c1dafcc"
}
],
But this wont work because I need it to be able to pull what model to use from the collectionType field

sorry for the late response , it seems like you are trying to populate the multilevel documents multilevel population.
here is an example.
db.statesPersonsSchema.find({ profileId }). populate({
path: 'activeWorkSchema',
populate: { path: 'referenceId' }
});

Related

mongodb/mongoose + graphql When referencing a model with the id, the data is null

My apologies if this doesn't make too much sense, I am starting to get more comfortable with Mongodb using mongoosejs and also using graphql. I am trying to create a db that will contain transactions made by users, said transactions should be part of a specific category.
These are my typedefs:
type Auth {
token: ID!
user: User
}
type User {
_id: ID
email: String
expenses: [Expense]
}
type Category {
_id: ID
name: String
}
type Expense {
_id: ID
expenseName: String
expenseAmount: Float
expenseDate: String
category: Category
}
type Query {
me: User
users: [User]
user(email: String!): User
expenses(email: String!): [Expense]
expense(id: ID!): Expense
categories: [Category]
}
type Mutation {
login(email: String!, password: String!): Auth
addCategory(name: String!): Category
addUser(email: String!, password: String!, firstName: String!, lastName: String!): Auth
addExpense(expenseName: String!, expenseAmount: Float!, categoryId: ID!): Expense
}
And my resolver for the expense/transaction:
addExpense: async (parent, args, context) => {
if(context.user) {
const expense = await Expense.create({
expenseName: args.expenseName,
expenseAmount: args.expenseAmount,
category: args.categoryId,
});
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { expenses: expense._id } },
{ new: true }
);
return expense;
}
throw new AuthenticationError("You must be logged in!");
}
Just in case you need it, these are my schemas:
For the user schema:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
match: [/.+#.+\..+/, "Must match an email address!"],
},
password: {
type: String,
required: true,
minlength: [8, "Password must be at least 8 characters long!"],
},
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
expenses: [
{
type: Schema.Types.ObjectId,
ref: "Expense",
}
]
});
For the expense/transaction:
const expenseSchema = new Schema({
expenseName: {
type: String,
required: true,
minlenght: 1,
},
expenseAmount: {
type: Number,
required: true,
min: 0.01,
},
expenseDate: {
type: Date,
default: Date.now,
},
category: {
type: Schema.Types.ObjectId,
ref: "Category",
required: true,
}
});
For the category creation:
const categorySchema = new Schema({
name: {
type: String,
required: true,
},
});
So when I run it in apollo server after adding an expense/transaction, it shows everything but it will not show the name for the category, it will always show null:
"data": {
"me": {
"_id": "630a42ea7f7be79ba1d4114c",
"email": "admin#test.com",
"expenses": [
{
"_id": "630af7b795e5fd336ba708be",
"expenseName": "Mom",
"expenseAmount": 70,
"expenseDate": "1661663159375",
"category": {
"_id": "630af5fd2d4b15974802c026",
"name": null
}
},
{
"_id": "630af85695e5fd336ba708c2",
"expenseName": "Mom",
"expenseAmount": 70,
"expenseDate": "1661663318985",
"category": {
"_id": "630a432b7f7be79ba1d4114e",
"name": null
}
}
]
}
}
}
Could someone help me figure out what am I missing? Apologies if I am sharing too much

How to develop nested condition query in mongoDB

I am pretty new to mongoDb and want to apply nested query.
I have a business schema like this:
const businessSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
businessType: {
type: Schema.Types.ObjectId,
ref: "businessCategory",
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
select: false,
},
review: {
type: [reviewSchema],
},
isDeleted: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
Business has a review where user can do the review and reviewSchema is
const reviewSchema = new mongoose.Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "users",
required: true,
},
rating: {
type: Number,
enum: [1, 2, 3, 4, 5],
},
reviewArray: {
type: [singleReviewSchema],
},
},
{ timestamps: true }
);
One user can do many reviews, and it has reviewArray.
ReviewArray schema is
const singleReviewSchema = new mongoose.Schema(
{
title: {
type: String,
},
description: {
type: String,
},
isDeleted: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
How to fetch the business with a condition business: isDeleted:false and its reviews with singleReviewSchema: isDeleted:false
I dont know your model names, so please replace path with correct names
but it might look like:
businnesModel.find({isDeleted: false})
.populate({
path: 'reviewModelName',
model: 'review',
populate: {
path: 'reviewArray',
model: 'singleReviewModelName',
match: {
isDeleted : false
}
}
})
It should provide you array of businessModel documents - even when their singleReviews array will be empty (because all of reviews are deleted, or there was zero reviews). So you have to filter it out in JS.
To avoid filtering in JS, and to do it a bit more efficient way for mongodb, you can go with aggregate instead.

How to populate object array in mongoose?

I wanted to populate 'item' in here and I'm getting the below error. It is an object array. This method worked for a normal array but gave an error for an object array. How to resolve it?
// Get the reserved list
const reservedDetails = await reserveInventory
.findOne({ memberID: id })
.select("itemsList")
.populate({
path: "item",
model: inventoryItem,
});
Error:
Cannot populate path `item` because it is not in your schema. Set the `strictPopulate` option to false to override.
reserveInventory Model:
const reserveInventorySchema = mongoose.Schema({
memberID: {
type: String,
ref: "member",
required: true,
},
itemsList: [
{
item: {
type: String,
ref: "inventoryItem",
},
quantity: {
type: Number,
},
},
],
});
module.exports = mongoose.model("reserveInventory", reserveInventorySchema);
inventoryItem Model:
const inventoryItemSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
available: {
type: Number,
required: true,
},
},
{
timestamps: true,
}
);
module.exports = mongoose.model("inventoryItem", inventoryItemSchema);
you got it wrong here
// Get the reserved list
const reservedDetails = await reserveInventory
.findOne({ memberID: id })
.select("itemsList")
.populate({ path: "itemsList.item"});

Return the actual document instead of ObjectId

So, I have a model called Drivers that receive a field called "user", which references a document from another model, like this:
const DriversSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
},
{
timestamps: true,
}
);
// ...
Querying the collection works as expected, here's an example:
Drivers.find({});
// returns ->
[
{
"name": "John",
"user": "5e43f8ad2fbb1d0035d5a154",
}
]
Is there a way to return the actual document represented by the 'user' field?
Thanks!

Mongoose not populating previously saved document with reference to newly saved document

all.
I am writing a MEAN stack application, using Mongoose (4.0.6) with Node/Express to interface with MongoDB, and I am running into difficulty populating saved documents when I later save new documents that the existing document should have a reference to. Specifically, in the app I have a user create an instance of a company before creating their admin account for that company, so when the user registers him/herself as an admin, I'd like the company document to populate its users array with the new user.
Here are my schemas for company and user:
User.js...
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;
var userSchema = new Schema({
first_name: { type: String, required: '{PATH} is required!' },
last_name: { type: String, required: '{PATH} is required!' },
username: { type: String, required: '{PATH} is required!', lowercase: true, unique: true },
password: { type: String, required: '{PATH} is required!' },
roles: { type: [String] },
company: { type: ObjectId, ref: 'Company', required: true },
db_permissions: [{ type: ObjectId, ref: 'DataConnection' }],
created_by: { type: ObjectId, ref: 'User' },
created_at: { type: Date, default: Date.now },
updated_at: [{ type: Date, default: Date.now }]
});
var User = mongoose.model('User', userSchema);
module.exports = {
User: User
};
Company.js...
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;
var companySchema = new Schema({
name: { type: String, uppercase: true, required: '{PATH} is required!', unique: true },
industry: { type: String, required: '{PATH} is required!' },
phone: { type: String, required: '{PATH} is required!' },
address_line_1: { type: String, uppercase: true, required: '{PATH} is required!' },
address_line_2: { type: String, uppercase: true },
city: { type: String, uppercase: true, required: '{PATH} is required!' },
state_prov: { type: String, uppercase: true, required: '{PATH} is required!' },
postal_code: { type: String, required: '{PATH} is required!' },
country: { type: String, required: '{PATH} is required!' },
logo_url: String,
users: [{ type: ObjectId, ref: 'User' }],
data_connections: [{ type: ObjectId, ref: 'DataConnection' }],
created_at: { type: Date, default: Date.now },
updated_at: [{ type: Date, default: Date.now }]
});
var Company = mongoose.model('Company', companySchema);
module.exports = {
Company: Company
};
Here is the code in my controller:
User.create(userData, function(err, user) {
if(err) {
if(err.toString().indexOf('E11000') > -1) {
err = new Error('Duplicate email');
}
res.status(400);
return res.send({ reason:err.toString() });
}
console.log('company id: ' + user.company);
Company.findById(user.company)
.populate({path: 'users'})
.exec(function (err, company) {
if (err) return handleError(err);
console.log(company.name + '\'s users now includes ' + company.users);
});
res.send(user);
The company (e.g. TEST53) saves to the database correctly with an empty users array:
{
"_id": "55ae421bf469f1b97bb52d5a",
"name": "TEST53",
"industry": "Construction",
"phone": "2352626254",
"city": "GDFGD",
"country": "United States",
"address_line_1": "DSGDFGH",
"state_prov": "GF",
"postal_code": "45645",
"logo_url": "",
"__v": 0,
"updated_at": [
"2015-07-21T12:59:07.609Z"
],
"created_at": "2015-07-21T12:59:07.597Z",
"data_connections": [],
"users": []
}
Then when I create the user, it saves correctly:
{
"_id": "55ae4238f469f1b97bb52d5b",
"username": "test53#test.com",
"password": "$2a$12$ZB6L1NCZEhLfjs99yUUNNOQEknyQmX6nP2BxBvo1uZGlvk9LlKGFu",
"company": "55ae421bf469f1b97bb52d5a",
"first_name": "Test53",
"last_name": "Admin",
"__v": 0,
"updated_at": [
"2015-07-21T12:59:36.925Z"
],
"created_at": "2015-07-21T12:59:36.550Z",
"db_permissions": [],
"roles": [
"admin"
]
}
And I can see that the correct ObjectId prints to the console for user.company:
company id: 55ae421bf469f1b97bb52d5a
But the company's users array doesn't populate with the user's id, and the console.log inside the .exec function prints 'TEST53's users now includes '.
I have tried several ways of wiring this up, with just 'users' instead of { path: 'users' }, writing a function that pushes the user into the array, using .run instead of .exec, but so far without success.
Is there something obvious I'm missing? Thanks in advance for any suggestions!
You're not actually adding the user to the company.
Try this:
Company.findById(user.company, function (err, company) {
if (err) return handleError(err);
// Add the user to the company's list of users.
company.users.push(user);
// Need to save again.
company.save(function(err) {
if (err) return handleError(err);
console.log(company.name + '\'s users now includes ' + company.users);
});
});
res.send(user);
It seems to me that all you want to do is to update the Company model to add the user, as opposed to actually use the (populated) Company document as a response, so I left out an additional Company.findById(...).populate(...) call.